/*!
    \file generator_javascript.cpp
    \brief Fast binary encoding JavaScript generator implementation
    \author Ivan Shynkarenka
    \date 28.06.2018
    \copyright MIT License
*/

#include "generator_javascript.h"

namespace FBE {

void GeneratorJavaScript::Generate(const std::shared_ptr<Package>& package)
{
    GeneratePackage(package);
}

void GeneratorJavaScript::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(//------------------------------------------------------------------------------
// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// FBE version: _VERSION_
//------------------------------------------------------------------------------
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFooter()
{

}

void GeneratorJavaScript::GenerateBig(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "big.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable */

/*
 *  big.js v5.1.2
 *  A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic.
 *  Copyright (c) 2017 Michael Mclaughlin <M8ch88l@gmail.com>
 *  https://github.com/MikeMcl/big.js/LICENCE
 */
;(function (GLOBAL) {
  'use strict';
  var Big,


/************************************** EDITABLE DEFAULTS *****************************************/


    // The default values below must be integers within the stated ranges.

    /*
     * The maximum number of decimal places (DP) of the results of operations involving division:
     * div and sqrt, and pow with negative exponents.
     */
    DP = 20,          // 0 to MAX_DP

    /*
     * The rounding mode (RM) used when rounding to the above decimal places.
     *
     *  0  Towards zero (i.e. truncate, no rounding).       (ROUND_DOWN)
     *  1  To nearest neighbour. If equidistant, round up.  (ROUND_HALF_UP)
     *  2  To nearest neighbour. If equidistant, to even.   (ROUND_HALF_EVEN)
     *  3  Away from zero.                                  (ROUND_UP)
     */
    RM = 1,             // 0, 1, 2 or 3

    // The maximum value of DP and Big.DP.
    MAX_DP = 1E6,       // 0 to 1000000

    // The maximum magnitude of the exponent argument to the pow method.
    MAX_POWER = 1E6,    // 1 to 1000000

    /*
     * The negative exponent (NE) at and beneath which toString returns exponential notation.
     * (JavaScript numbers: -7)
     * -1000000 is the minimum recommended exponent value of a Big.
     */
    NE = -7,            // 0 to -1000000

    /*
     * The positive exponent (PE) at and above which toString returns exponential notation.
     * (JavaScript numbers: 21)
     * 1000000 is the maximum recommended exponent value of a Big.
     * (This limit is not enforced or checked.)
     */
    PE = 21,            // 0 to 1000000


/**************************************************************************************************/
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(

    // Error messages.
    NAME = '[big.js] ',
    INVALID = NAME + 'Invalid ',
    INVALID_DP = INVALID + 'decimal places',
    INVALID_RM = INVALID + 'rounding mode',
    DIV_BY_ZERO = NAME + 'Division by zero',

    // The shared prototype object.
    P = {},
    UNDEFINED = void 0,
    NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;


  /*
   * Create and return a Big constructor.
   *
   */
  function _Big_() {

    /*
     * The Big constructor and exported function.
     * Create and return a new instance of a Big number object.
     *
     * n {number|string|Big} A numeric value.
     */
    function Big(n) {
      var x = this;

      // Enable constructor usage without new.
      if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);

      // Duplicate.
      if (n instanceof Big) {
        x.s = n.s;
        x.e = n.e;
        x.c = n.c.slice();
      } else {
        parse(x, n);
      }

      /*
       * Retain a reference to this Big constructor, and shadow Big.prototype.constructor which
       * points to Object.
       */
      x.constructor = Big;
    }

    Big.prototype = P;
    Big.DP = DP;
    Big.RM = RM;
    Big.NE = NE;
    Big.PE = PE;
    Big.version = '5.0.2';

    return Big;
  }


  /*
   * Parse the number or string value passed to a Big constructor.
   *
   * x {Big} A Big number instance.
   * n {number|string} A numeric value.
   */
  function parse(x, n) {
    var e, i, nl;

    // Minus zero?
    if (n === 0 && 1 / n < 0) n = '-0';
    else if (!NUMERIC.test(n += '')) throw Error(INVALID + 'number');

    // Determine sign.
    x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;

    // Decimal point?
    if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');

    // Exponential form?
    if ((i = n.search(/e/i)) > 0) {

      // Determine exponent.
      if (e < 0) e = i;
      e += +n.slice(i + 1);
      n = n.substring(0, i);
    } else if (e < 0) {

      // Integer.
      e = n.length;
    }

    nl = n.length;

    // Determine leading zeros.
    for (i = 0; i < nl && n.charAt(i) == '0';) ++i;

    if (i == nl) {

      // Zero.
      x.c = [x.e = 0];
    } else {

      // Determine trailing zeros.
      for (; nl > 0 && n.charAt(--nl) == '0';);
      x.e = e - i - 1;
      x.c = [];

      // Convert string to array of digits without leading/trailing zeros.
      for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
    }

    return x;
  }


  /*
   * Round Big x to a maximum of dp decimal places using rounding mode rm.
   * Called by stringify, P.div, P.round and P.sqrt.
   *
   * x {Big} The Big to round.
   * dp {number} Integer, 0 to MAX_DP inclusive.
   * rm {number} 0, 1, 2 or 3 (DOWN, HALF_UP, HALF_EVEN, UP)
   * [more] {boolean} Whether the result of division was truncated.
   */
  function round(x, dp, rm, more) {
    var xc = x.c,
      i = x.e + dp + 1;

    if (i < xc.length) {
      if (rm === 1) {

        // xc[i] is the digit after the digit that may be rounded up.
        more = xc[i] >= 5;
      } else if (rm === 2) {
        more = xc[i] > 5 || xc[i] == 5 &&
          (more || i < 0 || xc[i + 1] !== UNDEFINED || xc[i - 1] & 1);
      } else if (rm === 3) {
        more = more || xc[i] !== UNDEFINED || i < 0;
      } else {
        more = false;
        if (rm !== 0) throw Error(INVALID_RM);
      }

      if (i < 1) {
        xc.length = 1;

        if (more) {

          // 1, 0.1, 0.01, 0.001, 0.0001 etc.
          x.e = -dp;
          xc[0] = 1;
        } else {

          // Zero.
          xc[0] = x.e = 0;
        }
      } else {

        // Remove any digits after the required decimal places.
        xc.length = i--;

        // Round up?
        if (more) {

          // Rounding up may mean the previous digit has to be rounded up.
          for (; ++xc[i] > 9;) {
            xc[i] = 0;
            if (!i--) {
              ++x.e;
              xc.unshift(1);
            }
          }
        }

        // Remove trailing zeros.
        for (i = xc.length; !xc[--i];) xc.pop();
      }
    } else if (rm < 0 || rm > 3 || rm !== ~~rm) {
      throw Error(INVALID_RM);
    }

    return x;
  }


  /*
   * Return a string representing the value of Big x in normal or exponential notation.
   * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf.
   *
   * x {Big}
   * id? {number} Caller id.
   *         1 toExponential
   *         2 toFixed
   *         3 toPrecision
   *         4 valueOf
   * n? {number|undefined} Caller's argument.
   * k? {number|undefined}
   */
  function stringify(x, id, n, k) {
    var e, s,
      Big = x.constructor,
      z = !x.c[0];

    if (n !== UNDEFINED) {
      if (n !== ~~n || n < (id == 3) || n > MAX_DP) {
        throw Error(id == 3 ? INVALID + 'precision' : INVALID_DP);
      }

      x = new Big(x);

      // The index of the digit that may be rounded up.
      n = k - x.e;

      // Round?
      if (x.c.length > ++k) round(x, n, Big.RM);

      // toFixed: recalculate k as x.e may have changed if value rounded up.
      if (id == 2) k = x.e + n + 1;

      // Append zeros?
      for (; x.c.length < k;) x.c.push(0);
    }

    e = x.e;
    s = x.c.join('');
    n = s.length;

    // Exponential notation?
    if (id != 2 && (id == 1 || id == 3 && k <= e || e <= Big.NE || e >= Big.PE)) {
      s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e;

    // Normal notation.
    } else if (e < 0) {
      for (; ++e;) s = '0' + s;
      s = '0.' + s;
    } else if (e > 0) {
      if (++e > n) for (e -= n; e--;) s += '0';
      else if (e < n) s = s.slice(0, e) + '.' + s.slice(e);
    } else if (n > 1) {
      s = s.charAt(0) + '.' + s.slice(1);
    }

    return x.s < 0 && (!z || id == 4) ? '-' + s : s;
  }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(

  // Prototype/instance methods


  /*
   * Return a new Big whose value is the absolute value of this Big.
   */
  P.abs = function () {
    var x = new this.constructor(this);
    x.s = 1;
    return x;
  };


  /*
   * Return 1 if the value of this Big is greater than the value of Big y,
   *       -1 if the value of this Big is less than the value of Big y, or
   *        0 if they have the same value.
  */
  P.cmp = function (y) {
    var isneg,
      x = this,
      xc = x.c,
      yc = (y = new x.constructor(y)).c,
      i = x.s,
      j = y.s,
      k = x.e,
      l = y.e;

    // Either zero?
    if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i;

    // Signs differ?
    if (i != j) return i;

    isneg = i < 0;

    // Compare exponents.
    if (k != l) return k > l ^ isneg ? 1 : -1;

    j = (k = xc.length) < (l = yc.length) ? k : l;

    // Compare digit by digit.
    for (i = -1; ++i < j;) {
      if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1;
    }

    // Compare lengths.
    return k == l ? 0 : k > l ^ isneg ? 1 : -1;
  };


  /*
   * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded,
   * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
   */
  P.div = function (y) {
    var x = this,
      Big = x.constructor,
      a = x.c,                  // dividend
      b = (y = new Big(y)).c,   // divisor
      k = x.s == y.s ? 1 : -1,
      dp = Big.DP;

    if (dp !== ~~dp || dp < 0 || dp > MAX_DP) throw Error(INVALID_DP);

    // Divisor is zero?
    if (!b[0]) throw Error(DIV_BY_ZERO);

    // Dividend is 0? Return +-0.
    if (!a[0]) return new Big(k * 0);

    var bl, bt, n, cmp, ri,
      bz = b.slice(),
      ai = bl = b.length,
      al = a.length,
      r = a.slice(0, bl),   // remainder
      rl = r.length,
      q = y,                // quotient
      qc = q.c = [],
      qi = 0,
      d = dp + (q.e = x.e - y.e) + 1;    // number of digits of the result

    q.s = k;
    k = d < 0 ? 0 : d;

    // Create version of divisor with leading zero.
    bz.unshift(0);

    // Add zeros to make remainder as long as divisor.
    for (; rl++ < bl;) r.push(0);

    do {

      // n is how many times the divisor goes into current remainder.
      for (n = 0; n < 10; n++) {

        // Compare divisor and remainder.
        if (bl != (rl = r.length)) {
          cmp = bl > rl ? 1 : -1;
        } else {
          for (ri = -1, cmp = 0; ++ri < bl;) {
            if (b[ri] != r[ri]) {
              cmp = b[ri] > r[ri] ? 1 : -1;
              break;
            }
          }
        }

        // If divisor < remainder, subtract divisor from remainder.
        if (cmp < 0) {

          // Remainder can't be more than 1 digit longer than divisor.
          // Equalise lengths using divisor with extra leading zero?
          for (bt = rl == bl ? b : bz; rl;) {
            if (r[--rl] < bt[rl]) {
              ri = rl;
              for (; ri && !r[--ri];) r[ri] = 9;
              --r[ri];
              r[rl] += 10;
            }
            r[rl] -= bt[rl];
          }

          for (; !r[0];) r.shift();
        } else {
          break;
        }
      }

      // Add the digit n to the result array.
      qc[qi++] = cmp ? n : ++n;

      // Update the remainder.
      if (r[0] && cmp) r[rl] = a[ai] || 0;
      else r = [a[ai]];

    } while ((ai++ < al || r[0] !== UNDEFINED) && k--);

    // Leading zero? Do not remove if result is simply zero (qi == 1).
    if (!qc[0] && qi != 1) {

      // There can't be more than one zero.
      qc.shift();
      q.e--;
    }

    // Round?
    if (qi > d) round(q, dp, Big.RM, r[0] !== UNDEFINED);

    return q;
  };


  /*
   * Return true if the value of this Big is equal to the value of Big y, otherwise return false.
   */
  P.eq = function (y) {
    return !this.cmp(y);
  };


  /*
   * Return true if the value of this Big is greater than the value of Big y, otherwise return
   * false.
   */
  P.gt = function (y) {
    return this.cmp(y) > 0;
  };


  /*
   * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise
   * return false.
   */
  P.gte = function (y) {
    return this.cmp(y) > -1;
  };


  /*
   * Return true if the value of this Big is less than the value of Big y, otherwise return false.
   */
  P.lt = function (y) {
    return this.cmp(y) < 0;
  };


  /*
   * Return true if the value of this Big is less than or equal to the value of Big y, otherwise
   * return false.
   */
  P.lte = function (y) {
    return this.cmp(y) < 1;
  };


  /*
   * Return a new Big whose value is the value of this Big minus the value of Big y.
   */
  P.minus = P.sub = function (y) {
    var i, j, t, xlty,
      x = this,
      Big = x.constructor,
      a = x.s,
      b = (y = new Big(y)).s;

    // Signs differ?
    if (a != b) {
      y.s = -b;
      return x.plus(y);
    }

    var xc = x.c.slice(),
      xe = x.e,
      yc = y.c,
      ye = y.e;

    // Either zero?
    if (!xc[0] || !yc[0]) {

      // y is non-zero? x is non-zero? Or both are zero.
      return yc[0] ? (y.s = -b, y) : new Big(xc[0] ? x : 0);
    }

    // Determine which is the bigger number. Prepend zeros to equalise exponents.
    if (a = xe - ye) {

      if (xlty = a < 0) {
        a = -a;
        t = xc;
      } else {
        ye = xe;
        t = yc;
      }

      t.reverse();
      for (b = a; b--;) t.push(0);
      t.reverse();
    } else {

      // Exponents equal. Check digit by digit.
      j = ((xlty = xc.length < yc.length) ? xc : yc).length;

      for (a = b = 0; b < j; b++) {
        if (xc[b] != yc[b]) {
          xlty = xc[b] < yc[b];
          break;
        }
      }
    }

    // x < y? Point xc to the array of the bigger number.
    if (xlty) {
      t = xc;
      xc = yc;
      yc = t;
      y.s = -y.s;
    }

    /*
     * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only
     * needs to start at yc.length.
     */
    if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;

    // Subtract yc from xc.
    for (b = i; j > a;) {
      if (xc[--j] < yc[j]) {
        for (i = j; i && !xc[--i];) xc[i] = 9;
        --xc[i];
        xc[j] += 10;
      }

      xc[j] -= yc[j];
    }

    // Remove trailing zeros.
    for (; xc[--b] === 0;) xc.pop();

    // Remove leading zeros and adjust exponent accordingly.
    for (; xc[0] === 0;) {
      xc.shift();
      --ye;
    }

    if (!xc[0]) {

      // n - n = +0
      y.s = 1;

      // Result must be zero.
      xc = [ye = 0];
    }

    y.c = xc;
    y.e = ye;

    return y;
  };


  /*
   * Return a new Big whose value is the value of this Big modulo the value of Big y.
   */
  P.mod = function (y) {
    var ygtx,
      x = this,
      Big = x.constructor,
      a = x.s,
      b = (y = new Big(y)).s;

    if (!y.c[0]) throw Error(DIV_BY_ZERO);

    x.s = y.s = 1;
    ygtx = y.cmp(x) == 1;
    x.s = a;
    y.s = b;

    if (ygtx) return new Big(x);

    a = Big.DP;
    b = Big.RM;
    Big.DP = Big.RM = 0;
    x = x.div(y);
    Big.DP = a;
    Big.RM = b;

    return this.minus(x.times(y));
  };


  /*
   * Return a new Big whose value is the value of this Big plus the value of Big y.
   */
  P.plus = P.add = function (y) {
    var t,
      x = this,
      Big = x.constructor,
      a = x.s,
      b = (y = new Big(y)).s;

    // Signs differ?
    if (a != b) {
      y.s = -b;
      return x.minus(y);
    }

    var xe = x.e,
      xc = x.c,
      ye = y.e,
      yc = y.c;

    // Either zero? y is non-zero? x is non-zero? Or both are zero.
    if (!xc[0] || !yc[0]) return yc[0] ? y : new Big(xc[0] ? x : a * 0);

    xc = xc.slice();

    // Prepend zeros to equalise exponents.
    // Note: Faster to use reverse than do unshifts.
    if (a = xe - ye) {
      if (a > 0) {
        ye = xe;
        t = yc;
      } else {
        a = -a;
        t = xc;
      }

      t.reverse();
      for (; a--;) t.push(0);
      t.reverse();
    }

    // Point xc to the longer array.
    if (xc.length - yc.length < 0) {
      t = yc;
      yc = xc;
      xc = t;
    }

    a = yc.length;

    // Only start adding at yc.length - 1 as the further digits of xc can be left as they are.
    for (b = 0; a; xc[a] %= 10) b = (xc[--a] = xc[a] + yc[a] + b) / 10 | 0;

    // No need to check for zero, as +x + +y != 0 && -x + -y != 0

    if (b) {
      xc.unshift(b);
      ++ye;
    }

    // Remove trailing zeros.
    for (a = xc.length; xc[--a] === 0;) xc.pop();

    y.c = xc;
    y.e = ye;

    return y;
  };


  /*
   * Return a Big whose value is the value of this Big raised to the power n.
   * If n is negative, round to a maximum of Big.DP decimal places using rounding
   * mode Big.RM.
   *
   * n {number} Integer, -MAX_POWER to MAX_POWER inclusive.
   */
  P.pow = function (n) {
    var x = this,
      one = new x.constructor(1),
      y = one,
      isneg = n < 0;

    if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) throw Error(INVALID + 'exponent');
    if (isneg) n = -n;

    for (;;) {
      if (n & 1) y = y.times(x);
      n >>= 1;
      if (!n) break;
      x = x.times(x);
    }

    return isneg ? one.div(y) : y;
  };


  /*
   * Return a new Big whose value is the value of this Big rounded to a maximum of dp decimal
   * places using rounding mode rm.
   * If dp is not specified, round to 0 decimal places.
   * If rm is not specified, use Big.RM.
   *
   * dp? {number} Integer, 0 to MAX_DP inclusive.
   * rm? 0, 1, 2 or 3 (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_UP)
   */
  P.round = function (dp, rm) {
    var Big = this.constructor;
    if (dp === UNDEFINED) dp = 0;
    else if (dp !== ~~dp || dp < 0 || dp > MAX_DP) throw Error(INVALID_DP);
    return round(new Big(this), dp, rm === UNDEFINED ? Big.RM : rm);
  };


  /*
   * Return a new Big whose value is the square root of the value of this Big, rounded, if
   * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
   */
  P.sqrt = function () {
    var r, c, t,
      x = this,
      Big = x.constructor,
      s = x.s,
      e = x.e,
      half = new Big(0.5);

    // Zero?
    if (!x.c[0]) return new Big(x);

    // Negative?
    if (s < 0) throw Error(NAME + 'No square root');

    // Estimate.
    s = Math.sqrt(x.toString());

    // Math.sqrt underflow/overflow?
    // Re-estimate: pass x to Math.sqrt as integer, then adjust the result exponent.
    if (s === 0 || s === 1 / 0) {
      c = x.c.join('');
      if (!(c.length + e & 1)) c += '0';
      r = new Big(Math.sqrt(c).toString());
      r.e = ((e + 1) / 2 | 0) - (e < 0 || e & 1);
    } else {
      r = new Big(s.toString());
    }

    e = r.e + (Big.DP += 4);

    // Newton-Raphson iteration.
    do {
      t = r;
      r = half.times(t.plus(x.div(t)));
    } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join(''));

    return round(r, Big.DP -= 4, Big.RM);
  };


  /*
   * Return a new Big whose value is the value of this Big times the value of Big y.
   */
  P.times = P.mul = function (y) {
    var c,
      x = this,
      Big = x.constructor,
      xc = x.c,
      yc = (y = new Big(y)).c,
      a = xc.length,
      b = yc.length,
      i = x.e,
      j = y.e;

    // Determine sign of result.
    y.s = x.s == y.s ? 1 : -1;

    // Return signed 0 if either 0.
    if (!xc[0] || !yc[0]) return new Big(y.s * 0);

    // Initialise exponent of result as x.e + y.e.
    y.e = i + j;

    // If array xc has fewer digits than yc, swap xc and yc, and lengths.
    if (a < b) {
      c = xc;
      xc = yc;
      yc = c;
      j = a;
      a = b;
      b = j;
    }

    // Initialise coefficient array of result with zeros.
    for (c = new Array(j = a + b); j--;) c[j] = 0;

    // Multiply.

    // i is initially xc.length.
    for (i = b; i--;) {
      b = 0;

      // a is yc.length.
      for (j = a + i; j > i;) {

        // Current sum of products at this digit position, plus carry.
        b = c[j] + yc[i] * xc[j - i - 1] + b;
        c[j--] = b % 10;

        // carry
        b = b / 10 | 0;
      }

      c[j] = (c[j] + b) % 10;
    }

    // Increment result exponent if there is a final carry, otherwise remove leading zero.
    if (b) ++y.e;
    else c.shift();

    // Remove trailing zeros.
    for (i = c.length; !c[--i];) c.pop();
    y.c = c;

    return y;
  };


  /*
   * Return a string representing the value of this Big in exponential notation to dp fixed decimal
   * places and rounded using Big.RM.
   *
   * dp? {number} Integer, 0 to MAX_DP inclusive.
   */
  P.toExponential = function (dp) {
    return stringify(this, 1, dp, dp);
  };


  /*
   * Return a string representing the value of this Big in normal notation to dp fixed decimal
   * places and rounded using Big.RM.
   *
   * dp? {number} Integer, 0 to MAX_DP inclusive.
   *
   * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'.
   * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'.
   */
  P.toFixed = function (dp) {
    return stringify(this, 2, dp, this.e + dp);
  };


  /*
   * Return a string representing the value of this Big rounded to sd significant digits using
   * Big.RM. Use exponential notation if sd is less than the number of digits necessary to represent
   * the integer part of the value in normal notation.
   *
   * sd {number} Integer, 1 to MAX_DP inclusive.
   */
  P.toPrecision = function (sd) {
    return stringify(this, 3, sd, sd - 1);
  };


  /*
   * Return a string representing the value of this Big.
   * Return exponential notation if this Big has a positive exponent equal to or greater than
   * Big.PE, or a negative exponent equal to or less than Big.NE.
   * Omit the sign for negative zero.
   */
  P.toString = function () {
    return stringify(this);
  };


  /*
   * Return a string representing the value of this Big.
   * Return exponential notation if this Big has a positive exponent equal to or greater than
   * Big.PE, or a negative exponent equal to or less than Big.NE.
   * Include the sign for negative zero.
   */
  P.valueOf = P.toJSON = function () {
    return stringify(this, 4);
  };


  // Export


  Big = _Big_();

  Big['default'] = Big.Big = Big;

  //AMD.
  if (typeof define === 'function' && define.amd) {
    define(function () { return Big; });

  // Node and other CommonJS-like environments that support module.exports.
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = Big;

  //Browser.
  } else {
    GLOBAL.Big = Big;
  }
})(this);
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateInt64(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "int64.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable prefer-const,no-loss-of-precision */
'use strict'

/**
 * Signed 64-bit integer value
 */
class Int64 {
  /**
   * Initialize signed 64-bit integer value with high & low parts
   * @param {number} low The low 32-bit part
   * @param {number} high The high 32-bit part
   * @constructor
   */
  constructor (low, high) {
    this.low = low | 0
    this.high = high | 0
  }

  /**
   * Zero value constant
   * @returns {!Int64}
   */
  static get ZERO () { return INT64_ZERO }

  /**
   * Minimal value constant
   * @returns {!Int64}
   */
  static get MIN () { return INT64_MIN }

  /**
   * Maximal value constant
   * @returns {!Int64}
   */
  static get MAX () { return INT64_MAX }

  /**
   * Is the specified object Int64?
   * @param obj Object to check
   * @returns {!boolean}
   */
  static isInt64 (obj) {
    return (obj && obj.__isInt64__) === true
  }

  /**
   * Returns Int64 representing the 64 bit integer that comes by concatenating the given low and high 32 bits parts
   * @param {number} lowBits The low 32-bit part
   * @param {number} highBits The high 32-bit part
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromBits (lowBits, highBits) {
    return new Int64(lowBits, highBits)
  }

  /**
   * Returns Int64 from its bytes representation
   * @param {!Array.<number>} bytes Bytes representation
   * @param {boolean=} le Whether little or big endian, defaults to little endian
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromBytes (bytes, le = true) {
    return le ? Int64.fromBytesLE(bytes) : Int64.fromBytesBE(bytes)
  }

  /**
   * Returns Int64 from its bytes representation (bid endian)
   * @param {!Array.<number>} bytes Bytes representation
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromBytesBE (bytes) {
    return new Int64(
      bytes[4] << 24 |
      bytes[5] << 16 |
      bytes[6] << 8 |
      bytes[7] << 0,
      bytes[0] << 24 |
      bytes[1] << 16 |
      bytes[2] << 8 |
      bytes[3] << 0
    )
  }

  /**
   * Returns Int64 from its bytes representation (little endian)
   * @param {!Array.<number>} bytes Bytes representation
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromBytesLE (bytes) {
    return new Int64(
      bytes[0] << 0 |
      bytes[1] << 8 |
      bytes[2] << 16 |
      bytes[3] << 24,
      bytes[4] << 0 |
      bytes[5] << 8 |
      bytes[6] << 16 |
      bytes[7] << 24
    )
  }

  /**
   * Returns Int64 representing the given 32-bit integer value
   * @param {number} value 32-bit integer value
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromInt32 (value) {
    value |= 0

    let cache = ((value >= -128) && (value < 128))
    if (cache) {
      let cached = Int64Cache[value]
      if (cached) {
        return cached
      }
    }

    let result = Int64.fromBits(value, (value < 0) ? -1 : 0)

    if (cache) {
      Int64Cache[value] = result
    }

    return result
  }

  /**
   * Returns Int64 representing the given value, provided that it is a finite number. Otherwise, zero is returned.
   * @param {number} value 32-bit integer value
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromNumber (value) {
    if (isNaN(value)) {
      return INT64_ZERO
    }
    if (value <= -INT64_TWO_PWR_63_DBL) {
      return INT64_MIN
    }
    if (value + 1 >= INT64_TWO_PWR_63_DBL) {
      return INT64_MAX
    }
    if (value < 0) {
      return Int64.fromNumber(-value).neg()
    }
    return Int64.fromBits((value % INT64_TWO_PWR_32_DBL) | 0, (value / INT64_TWO_PWR_32_DBL) | 0)
  }

  /**
   * Returns Int64 representation of the given string, written using the specified radix.
   * @param {string} str The textual representation of the Int64
   * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromString (str, radix = 10) {
    if (str.length === 0) {
      throw new Error('Empty string!')
    }
    if ((str === 'NaN') || (str === 'Infinity') || (str === '+Infinity') || (str === '-Infinity')) {
      return INT64_ZERO
    }
    radix = radix || 10
    if ((radix < 2) || (radix > 36)) {
      throw new RangeError('radix')
    }

    let p = str.indexOf('-')
    if (p > 0) {
      throw new Error('Interior hyphen!')
    } else if (p === 0) {
      return Int64.fromString(str.substring(1), radix).neg()
    }

    // Do several (8) digits each time through the loop, so as to
    // minimize the calls to the very expensive emulated div.
    let radixToPower = Int64.fromNumber(Math.pow(radix, 8))

    let result = INT64_ZERO
    for (let i = 0; i < str.length; i += 8) {
      let size = Math.min(8, str.length - i)
      let value = parseInt(str.substring(i, i + size), radix)
      if (size < 8) {
        let power = Int64.fromNumber(Math.pow(radix, size))
        result = result.mul(power).add(Int64.fromNumber(value))
      } else {
        result = result.mul(radixToPower)
        result = result.add(Int64.fromNumber(value))
      }
    }
    return result
  }

  /**
   * Converts the specified value to a Int64 using the appropriate from* function for its type.
   * @param {!Int64|!UInt64|number|string} value Value
   * @returns {!Int64} The corresponding Int64 value
   */
  static fromValue (value) {
    if (typeof value === 'number') {
      return Int64.fromNumber(value)
    }
    if (typeof value === 'string') {
      return Int64.fromString(value, 10)
    }
    return Int64.fromBits(value.low, value.high)
  }

  /**
   * Converts the Int64 to its bytes representation
   * @this {!Int64}
   * @param {boolean=} le Whether little or big endian, defaults to little endian
   * @returns {!Array.<number>} Bytes representation
   */
  toBytes (le = true) {
    return le ? this.toBytesLE() : this.toBytesBE()
  }

  /**
   * Converts the Int64 to its bytes representation (big endian)
   * @this {!Int64}
   * @returns {!Array.<number>} Bytes representation
   */
  toBytesBE () {
    let hi = this.high
    let lo = this.low
    return [
      hi >>> 24 & 0xFF,
      hi >>> 16 & 0xFF,
      hi >>> 8 & 0xFF,
      hi >>> 0 & 0xFF,
      lo >>> 24 & 0xFF,
      lo >>> 16 & 0xFF,
      lo >>> 8 & 0xFF,
      lo >>> 0 & 0xFF
    ]
  }

  /**
   * Converts the Int64 to its bytes representation (little endian)
   * @this {!Int64}
   * @returns {!Array.<number>} Bytes representation
   */
  toBytesLE () {
    let hi = this.high
    let lo = this.low
    return [
      lo >>> 0 & 0xFF,
      lo >>> 8 & 0xFF,
      lo >>> 16 & 0xFF,
      lo >>> 24 & 0xFF,
      hi >>> 0 & 0xFF,
      hi >>> 8 & 0xFF,
      hi >>> 16 & 0xFF,
      hi >>> 24 & 0xFF
    ]
  }

  /**
   * Converts the Int64 to a 32-bit integer, assuming it is a 32-bit integer
   * @this {!Int64}
   * @returns {number} Result 32-bit integer
   */
  toInt32 () {
    return this.low
  }

  /**
   * Converts the Int64 to a the nearest floating-point representation of this value (double, 53 bit mantissa)
   * @this {!Int64}
   * @returns {number} Result number
   */
  toNumber () {
    return this.high * INT64_TWO_PWR_32_DBL + (this.low >>> 0)
  }

  /**
   * Converts the Int64 to a string written in the specified radix
   * @this {!Int64}
   * @param {number=} radix Radix (2-36), defaults to 10
   * @returns {string} Result string
   */
  toString (radix = 10) {
    radix = radix || 10
    if ((radix < 2) || (radix > 36)) {
      throw new RangeError('Radix must be in range [2, 36]')
    }
    if (this.isZero) {
      return '0'
    }
    if (this.isNegative) {
      if (this.eq(INT64_MIN)) {
        // We need to change the Long value before it can be negated, so we remove
        // the bottom-most digit in this base and then recurse to do the rest.
        let radixLong = Int64.fromNumber(radix)
        let div = this.div(radixLong)
        let rem1 = div.mul(radixLong).sub(this)
        return div.toString(radix) + rem1.toInt32().toString(radix)
      } else {
        return '-' + this.neg().toString(radix)
      }
    }

    // Do several (6) digits each time through the loop, so as to
    // minimize the calls to the very expensive emulated div.
    let radixToPower = Int64.fromNumber(Math.pow(radix, 6))
    let rem = this
    let result = ''
    while (true) {
      let remDiv = rem.div(radixToPower)
      let intval = rem.sub(remDiv.mul(radixToPower)).toInt32() >>> 0
      let digits = intval.toString(radix)
      rem = remDiv
      if (rem.isZero) {
        return digits + result
      } else {
        while (digits.length < 6) {
          digits = '0' + digits
        }
        result = '' + digits + result
      }
    }
  }

  /**
   * Converts the Int64 to Int64
   * @this {!Int64}
   * @returns {!Int64} Result Int64 number
   */
  toSigned () {
    return this
  }

  /**
   * Converts the Int64 to UInt64
   * @this {!Int64}
   * @returns {!UInt64} Result UInt64 number
   */
  toUnsigned () {
    return UInt64.fromBits(this.low, this.high)
  }

  /**
   * Get the high 32 bits as a signed integer
   * @this {!Int64}
   * @returns {number} Signed high bits
   */
  get HighBits () {
    return this.high
  }

  /**
   * Get the high 32 bits as an unsigned integer
   * @this {!Int64}
   * @returns {number} Unsigned high bits
   */
  get HighBitsUnsigned () {
    return this.high >>> 0
  }

  /**
   * Get the low 32 bits as a signed integer
   * @this {!Int64}
   * @returns {number} Signed low bits
   */
  get LowBits () {
    return this.low
  }

  /**
   * Get the low 32 bits as an unsigned integer
   * @this {!Int64}
   * @returns {number} Unsigned low bits
   */
  get LowBitsUnsigned () {
    return this.low >>> 0
  }

  /**
   * Get the number of bits needed to represent the absolute value of this Int64
   * @this {!Int64}
   * @returns {number} Number of represented bits
   */
  get NumBits () {
    if (this.isNegative) {
      return this.eq(INT64_MIN) ? 64 : this.neg().NumBits
    }

    let val = (this.high !== 0) ? this.high : this.low

    let bit
    for (bit = 31; bit > 0; bit--) {
      if ((val & (1 << bit)) !== 0) {
        break
      }
    }

    return (this.high !== 0) ? (bit + 33) : (bit + 1)
  }

  /**
   * Is this value zero?
   * @this {!Int64}
   * @returns {boolean} Zero test result
   */
  get isZero () {
    return (this.high === 0) && (this.low === 0)
  }

  /**
   * Is this value positive?
   * @this {!Int64}
   * @returns {boolean} Positive test result
   */
  get isPositive () {
    return this.high >= 0
  }

  /**
   * Is this value negative?
   * @this {!Int64}
   * @returns {boolean} Negative test result
   */
  get isNegative () {
    return this.high < 0
  }

  /**
   * Is this value odd?
   * @this {!Int64}
   * @returns {boolean} Odd test result
   */
  get isOdd () {
    return (this.low & 1) === 1
  }

  /**
   * Is this value even?
   * @this {!Int64}
   * @returns {boolean} Even test result
   */
  get isEven () {
    return (this.low & 1) === 0
  }

  /**
   * Is this value equal to other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Equal result
   */
  eq (other) {
    if (UInt64.isUInt64(other)) {
      if (((this.high >>> 31) === 1) && ((other.high >>> 31) === 1)) {
        return false
      }
    } else if (!Int64.isInt64(other)) {
      other = Int64.fromValue(other)
    }
    return (this.high === other.high) && (this.low === other.low)
  }

  /**
   * Is this value not equal to other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Not equal result
   */
  ne (other) {
    return !this.eq(other)
  }

  /**
   * Is this value less than other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Less than result
   */
  lt (other) {
    return this.cmp(other) < 0
  }

  /**
   * Is this value less than or equal other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Less than or equal result
   */
  lte (other) {
    return this.cmp(other) <= 0
  }

  /**
   * Is this value greater than other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Greater than result
   */
  gt (other) {
    return this.cmp(other) > 0
  }

  /**
   * Is this value greater than or equal other one?
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Greater than or equal result
   */
  gte (other) {
    return this.cmp(other) >= 0
  }

  /**
   * Compare this value to other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {number} 0 if they are the same, 1 if the this is greater and -1 if the given one is greater
   */
  cmp (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    if (this.eq(other)) {
      return 0
    }
    let thisNeg = this.isNegative
    let otherNeg = other.isNegative
    if (thisNeg && !otherNeg) {
      return -1
    }
    if (!thisNeg && otherNeg) {
      return 1
    }
    // At this point the sign bits are the same
    return this.sub(other).isNegative ? -1 : 1
  }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
  /**
   * Negates this value
   * @this {!Int64}
   * @returns {!Int64} Negated value
   */
  neg () {
    if (this.eq(INT64_MIN)) {
      return INT64_MIN
    }
    return this.not().add(INT64_ONE)
  }

  /**
   * Add this value to other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Sum of the values
   */
  add (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }

    // Divide each number into 4 chunks of 16 bits, and then sum the chunks

    let a48 = this.high >>> 16
    let a32 = this.high & 0xFFFF
    let a16 = this.low >>> 16
    let a00 = this.low & 0xFFFF

    let b48 = other.high >>> 16
    let b32 = other.high & 0xFFFF
    let b16 = other.low >>> 16
    let b00 = other.low & 0xFFFF

    let c48 = 0
    let c32 = 0
    let c16 = 0
    let c00 = 0
    c00 += a00 + b00
    c16 += c00 >>> 16
    c00 &= 0xFFFF
    c16 += a16 + b16
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c32 += a32 + b32
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c48 += a48 + b48
    c48 &= 0xFFFF
    return Int64.fromBits((c16 << 16) | c00, (c48 << 16) | c32)
  }

  /**
   * Subtract other value from this value
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Difference of two values
   */
  sub (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    return this.add(other.neg())
  }

  /**
   * Multiply this value with other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Multiplication of two values
   */
  mul (other) {
    if (this.isZero) {
      return INT64_ZERO
    }
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }

    if (other.isZero) {
      return INT64_ZERO
    }
    if (this.eq(INT64_MIN)) {
      return other.isOdd ? INT64_MIN : INT64_ZERO
    }
    if (other.eq(INT64_MIN)) {
      return this.isOdd ? INT64_MIN : INT64_ZERO
    }

    if (this.isNegative) {
      if (other.isNegative) {
        return this.neg().mul(other.neg())
      } else {
        return this.neg().mul(other).neg()
      }
    } else if (other.isNegative) {
      return this.mul(other.neg()).neg()
    }

    // If both longs are small, use float multiplication
    if (this.lt(INT64_TWO_PWR_24) && other.lt(INT64_TWO_PWR_24)) {
      return Int64.fromNumber(this.toNumber() * other.toNumber())
    }

    // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
    // We can skip products that would overflow.

    let a48 = this.high >>> 16
    let a32 = this.high & 0xFFFF
    let a16 = this.low >>> 16
    let a00 = this.low & 0xFFFF

    let b48 = other.high >>> 16
    let b32 = other.high & 0xFFFF
    let b16 = other.low >>> 16
    let b00 = other.low & 0xFFFF

    let c48 = 0
    let c32 = 0
    let c16 = 0
    let c00 = 0
    c00 += a00 * b00
    c16 += c00 >>> 16
    c00 &= 0xFFFF
    c16 += a16 * b00
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c16 += a00 * b16
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c32 += a32 * b00
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c32 += a16 * b16
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c32 += a00 * b32
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48
    c48 &= 0xFFFF
    return Int64.fromBits((c16 << 16) | c00, (c48 << 16) | c32)
  }

  /**
   * Divide this value by other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Division of two values
   */
  div (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    if (other.isZero) {
      throw new Error('Division by zero!')
    }

    if (this.isZero) {
      return INT64_ZERO
    }

    let approx, rem, res

    // This section is only relevant for signed longs and is derived from the closure library as a whole
    if (this.eq(INT64_MIN)) {
      // Recall that -MIN_VALUE == MIN_VALUE
      if (other.eq(INT64_ONE) || other.eq(INT64_NEG_ONE)) {
        return INT64_MIN
      } else if (other.eq(INT64_MIN)) {
        return INT64_ONE
      } else {
        // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|
        let halfThis = this.shr(1)
        approx = halfThis.div(other).shl(1)
        if (approx.eq(INT64_ZERO)) {
          return other.isNegative ? INT64_ONE : INT64_NEG_ONE
        } else {
          rem = this.sub(other.mul(approx))
          res = approx.add(rem.div(other))
          return res
        }
      }
    } else if (other.eq(INT64_MIN)) {
      return INT64_ZERO
    }

    if (this.isNegative) {
      if (other.isNegative) {
        return this.neg().div(other.neg())
      }
      return this.neg().div(other).neg()
    } else if (other.isNegative) {
      return this.div(other.neg()).neg()
    }
    res = INT64_ZERO

    // Repeat the following until the remainder is less than other:  find a floating-point that approximates
    // remainder / other *from below*, add this into the result, and subtract it from the remainder.
    // It is critical that the approximate value is less than or equal to the real value so that the
    // remainder never becomes negative.
    rem = this
    while (rem.gte(other)) {
      // Approximate the result of division. This may be a little greater or smaller than the actual value
      approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()))

      // We will tweak the approximate result by changing it in the 48-th digit or the smallest non-fractional
      // digit, whichever is larger.
      let log2 = Math.ceil(Math.log(approx) / Math.LN2)
      let delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48)

      // Decrease the approximation until it is smaller than the remainder.
      // Note that if it is too large, the product overflows and is negative.
      let approxRes = Int64.fromNumber(approx)
      let approxRem = approxRes.mul(other)
      while (approxRem.isNegative || approxRem.gt(rem)) {
        approx -= delta
        approxRes = Int64.fromNumber(approx)
        approxRem = approxRes.mul(other)
      }

      // We know the answer can't be zero... and actually, zero would cause infinite recursion since
      // we would make no progress.
      if (approxRes.isZero) {
        approxRes = INT64_ONE
      }

      res = res.add(approxRes)
      rem = rem.sub(approxRem)
    }
    return res
  }

  /**
   * Modulo this value by other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Module of two values
   */
  mod (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    return this.sub(this.div(other).mul(other))
  }

  /**
   * Bitwise NOT this value
   * @this {!Int64}
   * @returns {!Int64} Bitwise NOT result value
   */
  not () {
    return Int64.fromBits(~this.low, ~this.high)
  }

  /**
   * Bitwise AND this value and other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Bitwise AND result value
   */
  and (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    return Int64.fromBits(this.low & other.low, this.high & other.high)
  }

  /**
   * Bitwise OR this value and other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Bitwise OR result value
   */
  or (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    return Int64.fromBits(this.low | other.low, this.high | other.high)
  }

  /**
   * Bitwise XOR this value and other one
   * @this {!Int64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!Int64} Bitwise XOR result value
   */
  xor (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = Int64.fromValue(other)
    }
    return Int64.fromBits(this.low ^ other.low, this.high ^ other.high)
  }

  /**
   * This Int64 with bits shifted to the left by the given amount
   * @this {!Int64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!Int64} Shifted result value
   */
  shl (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    if ((numBits &= 63) === 0) {
      return this
    } else if (numBits < 32) {
      return Int64.fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)))
    } else {
      return Int64.fromBits(0, this.low << (numBits - 32))
    }
  }

  /**
   * This Int64 with bits shifted to the right by the given amount
   * @this {!Int64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!Int64} Shifted result value
   */
  shr (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    if ((numBits &= 63) === 0) {
      return this
    } else if (numBits < 32) {
      return Int64.fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits)
    } else {
      return Int64.fromBits(this.high >> (numBits - 32), (this.high >= 0) ? 0 : -1)
    }
  }

  /**
   * This Int64 with bits shifted to the right by the given amount with filling zeros to the left
   * @this {!Int64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!Int64} Shifted result value
   */
  shru (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    numBits &= 63
    if (numBits === 0) {
      return this
    } else {
      let high = this.high
      if (numBits < 32) {
        let low = this.low
        return Int64.fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits)
      } else if (numBits === 32) {
        return Int64.fromBits(high, 0)
      } else {
        return Int64.fromBits(high >>> (numBits - 32), 0)
      }
    }
  }
}

Object.defineProperty(Int64.prototype, '__isInt64__', { value: true })

// Useful constants
const INT64_TWO_PWR_16_DBL = 1 << 16
const INT64_TWO_PWR_24_DBL = 1 << 24
const INT64_TWO_PWR_32_DBL = INT64_TWO_PWR_16_DBL * INT64_TWO_PWR_16_DBL
const INT64_TWO_PWR_64_DBL = INT64_TWO_PWR_32_DBL * INT64_TWO_PWR_32_DBL
const INT64_TWO_PWR_63_DBL = INT64_TWO_PWR_64_DBL / 2

// Int64 values cache
const Int64Cache = {}

// Int64 values constants
const INT64_ZERO = Int64.fromInt32(0)
const INT64_ONE = Int64.fromInt32(1)
const INT64_NEG_ONE = Int64.fromInt32(-1)
const INT64_MIN = Int64.fromBits(0, 0x80000000 | 0)
const INT64_MAX = Int64.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0)
const INT64_TWO_PWR_24 = Int64.fromInt32(INT64_TWO_PWR_24_DBL)

exports.Int64 = Int64
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
/**
 * Unsigned 64-bit integer value
 */
class UInt64 {
  /**
   * Initialize unsigned 64-bit integer value with high & low parts
   * @param {number} low The low 32-bit part
   * @param {number} high The high 32-bit part
   * @constructor
   */
  constructor (low, high) {
    this.low = low | 0
    this.high = high | 0
  }

  /**
   * Zero value constant
   * @returns {!UInt64}
   */
  static get ZERO () { return UINT64_ZERO }

  /**
   * Minimal value constant
   * @returns {!UInt64}
   */
  static get MIN () { return UINT64_MIN }

  /**
   * Maximal value constant
   * @returns {!UInt64}
   */
  static get MAX () { return UINT64_MAX }

  /**
   * Is the specified object UInt64?
   * @param obj Object to check
   * @returns {boolean}
   */
  static isUInt64 (obj) {
    return (obj && obj.__isUInt64__) === true
  }

  /**
   * Returns UInt64 representing the 64 bit integer that comes by concatenating the given low and high 32 bits parts
   * @param {number} lowBits The low 32-bit part
   * @param {number} highBits The high 32-bit part
   * @returns {!UInt64} The corresponding UInt64 value
   */
  static fromBits (lowBits, highBits) {
    return new UInt64(lowBits, highBits)
  }

  /**
   * Returns UInt64 from its bytes representation
   * @param {!Array.<number>} bytes Bytes representation
   * @param {boolean=} le Whether little or big endian, defaults to little endian
   * @returns {!UInt64} The corresponding Int64 value
   */
  static fromBytes (bytes, le = true) {
    return le ? UInt64.fromBytesLE(bytes) : UInt64.fromBytesBE(bytes)
  }

  /**
   * Returns UInt64 from its bytes representation (bid endian)
   * @param {!Array.<number>} bytes Bytes representation
   * @returns {!UInt64} The corresponding Int64 value
   */
  static fromBytesBE (bytes) {
    return new UInt64(
      bytes[4] << 24 |
      bytes[5] << 16 |
      bytes[6] << 8 |
      bytes[7] << 0,
      bytes[0] << 24 |
      bytes[1] << 16 |
      bytes[2] << 8 |
      bytes[3] << 0
    )
  }

  /**
   * Returns UInt64 from its bytes representation (little endian)
   * @param {!Array.<number>} bytes Bytes representation
   * @returns {!UInt64} The corresponding Int64 value
   */
  static fromBytesLE (bytes) {
    return new UInt64(
      bytes[0] << 0 |
      bytes[1] << 8 |
      bytes[2] << 16 |
      bytes[3] << 24,
      bytes[4] << 0 |
      bytes[5] << 8 |
      bytes[6] << 16 |
      bytes[7] << 24
    )
  }

  /**
   * Returns UInt64 representing the given 32-bit integer value
   * @param {number} value 32-bit integer value
   * @returns {!UInt64} The corresponding UInt64 value
   */
  static fromInt32 (value) {
    value >>>= 0

    let cache = ((value >= 0) && (value < 256))
    if (cache) {
      let cached = UInt64Cache[value]
      if (cached) {
        return cached
      }
    }

    let result = UInt64.fromBits(value, ((value | 0) < 0) ? -1 : 0)

    if (cache) {
      UInt64Cache[value] = result
    }

    return result
  }

  /**
   * Returns UInt64 representing the given value, provided that it is a finite number. Otherwise, zero is returned.
   * @param {number} value 32-bit integer value
   * @returns {!UInt64} The corresponding UInt64 value
   */
  static fromNumber (value) {
    if (isNaN(value)) {
      return UINT64_ZERO
    }
    if (value < 0) {
      return UINT64_ZERO
    }
    if (value >= UINT64_TWO_PWR_64_DBL) {
      return UINT64_MAX
    }
    if (value < 0) {
      return UInt64.fromNumber(-value).neg()
    }
    return UInt64.fromBits((value % UINT64_TWO_PWR_32_DBL) | 0, (value / UINT64_TWO_PWR_32_DBL) | 0)
  }

  /**
   * Returns UInt64 representation of the given string, written using the specified radix.
   * @param {string} str The textual representation of the UInt64
   * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
   * @returns {!UInt64} The corresponding UInt64 value
   */
  static fromString (str, radix = 10) {
    if (str.length === 0) {
      throw new Error('Empty string!')
    }
    if ((str === 'NaN') || (str === 'Infinity') || (str === '+Infinity') || (str === '-Infinity')) {
      return UINT64_ZERO
    }
    radix = radix || 10
    if ((radix < 2) || (radix > 36)) {
      throw new RangeError('radix')
    }

    let p = str.indexOf('-')
    if (p > 0) {
      throw new Error('Interior hyphen!')
    } else if (p === 0) {
      return UInt64.fromString(str.substring(1), radix).neg()
    }

    // Do several (8) digits each time through the loop, so as to
    // minimize the calls to the very expensive emulated div.
    let radixToPower = UInt64.fromNumber(Math.pow(radix, 8))

    let result = UINT64_ZERO
    for (let i = 0; i < str.length; i += 8) {
      let size = Math.min(8, str.length - i)
      let value = parseInt(str.substring(i, i + size), radix)
      if (size < 8) {
        let power = UInt64.fromNumber(Math.pow(radix, size))
        result = result.mul(power).add(UInt64.fromNumber(value))
      } else {
        result = result.mul(radixToPower)
        result = result.add(UInt64.fromNumber(value))
      }
    }
    return result
  }

  /**
   * Converts the specified value to a UInt64 using the appropriate from* function for its type.
   * @param {!Int64|!UInt64|number|string} value Value
   * @returns {!UInt64} The corresponding UInt64 value
   */
  static fromValue (value) {
    if (typeof value === 'number') {
      return UInt64.fromNumber(value)
    }
    if (typeof value === 'string') {
      return UInt64.fromString(value, 10)
    }
    return UInt64.fromBits(value.low, value.high)
  }

  /**
   * Converts the UInt64 to its bytes representation
   * @this {!UInt64}
   * @param {boolean=} le Whether little or big endian, defaults to little endian
   * @returns {!Array.<number>} Bytes representation
   */
  toBytes (le = true) {
    return le ? this.toBytesLE() : this.toBytesBE()
  }

  /**
   * Converts the UInt64 to its bytes representation (big endian)
   * @this {!UInt64}
   * @returns {!Array.<number>} Bytes representation
   */
  toBytesBE () {
    let hi = this.high
    let lo = this.low
    return [
      hi >>> 24 & 0xff,
      hi >>> 16 & 0xff,
      hi >>> 8 & 0xff,
      hi >>> 0 & 0xff,
      lo >>> 24 & 0xff,
      lo >>> 16 & 0xff,
      lo >>> 8 & 0xff,
      lo >>> 0 & 0xff
    ]
  }

  /**
   * Converts the UInt64 to its bytes representation (little endian)
   * @this {!UInt64}
   * @returns {!Array.<number>} Bytes representation
   */
  toBytesLE () {
    let hi = this.high
    let lo = this.low
    return [
      lo >>> 0 & 0xff,
      lo >>> 8 & 0xff,
      lo >>> 16 & 0xff,
      lo >>> 24 & 0xff,
      hi >>> 0 & 0xff,
      hi >>> 8 & 0xff,
      hi >>> 16 & 0xff,
      hi >>> 24 & 0xff
    ]
  }

  /**
   * Converts the UInt32 to a 32-bit integer, assuming it is a 32-bit integer
   * @this {!UInt64}
   * @returns {number} Result 32-bit integer
   */
  toInt32 () {
    return this.low >>> 0
  }

  /**
   * Converts the UInt64 to a the nearest floating-point representation of this value (double, 53 bit mantissa)
   * @this {!UInt64}
   * @returns {number} Result number
   */
  toNumber () {
    return ((this.high >>> 0) * UINT64_TWO_PWR_32_DBL) + (this.low >>> 0)
  }

  /**
   * Converts the UInt64 to a string written in the specified radix
   * @this {!UInt64}
   * @param {number=} radix Radix (2-36), defaults to 10
   * @returns {string} Result string
   */
  toString (radix = 10) {
    radix = radix || 10
    if ((radix < 2) || (radix > 36)) {
      throw new RangeError('Radix must be in range [2, 36]')
    }
    if (this.isZero) {
      return '0'
    }
    if (this.isNegative) {
      if (this.eq(UINT64_MIN)) {
        // We need to change the Long value before it can be negated, so we remove
        // the bottom-most digit in this base and then recurse to do the rest.
        let radixLong = UInt64.fromNumber(radix)
        let div = this.div(radixLong)
        let rem1 = div.mul(radixLong).sub(this)
        return div.toString(radix) + rem1.toInt32().toString(radix)
      } else {
        return '-' + this.neg().toString(radix)
      }
    }

    // Do several (6) digits each time through the loop, so as to
    // minimize the calls to the very expensive emulated div.
    let radixToPower = UInt64.fromNumber(Math.pow(radix, 6))
    let rem = this
    let result = ''
    while (true) {
      let remDiv = rem.div(radixToPower)
      let intval = rem.sub(remDiv.mul(radixToPower)).toInt32() >>> 0
      let digits = intval.toString(radix)
      rem = remDiv
      if (rem.isZero) {
        return digits + result
      } else {
        while (digits.length < 6) {
          digits = '0' + digits
        }
        result = '' + digits + result
      }
    }
  }

  /**
   * Converts the UInt64 to Int64
   * @this {!UInt64}
   * @returns {!Int64} Result Int64 number
   */
  toSigned () {
    return Int64.fromBits(this.low, this.high)
  }

  /**
   * Converts the UInt64 to UInt64
   * @this {!UInt64}
   * @returns {!UInt64} Result UInt64 number
   */
  toUnsigned () {
    return this
  }

  /**
   * Gets the high 32 bits as a signed integer
   * @this {!UInt64}
   * @returns {number} Signed high bits
   */
  get HighBits () {
    return this.high
  }

  /**
   * Gets the high 32 bits as an unsigned integer
   * @this {!UInt64}
   * @returns {number} Unsigned high bits
   */
  get HighBitsUnsigned () {
    return this.high >>> 0
  }

  /**
   * Gets the low 32 bits as a signed integer
   * @this {!UInt64}
   * @returns {number} Signed low bits
   */
  get LowBits () {
    return this.low
  }

  /**
   * Gets the low 32 bits as an unsigned integer
   * @this {!UInt64}
   * @returns {number} Unsigned low bits
   */
  get LowBitsUnsigned () {
    return this.low >>> 0
  }

  /**
   * Gets the number of bits needed to represent the absolute value of this UInt64
   * @this {!UInt64}
   * @returns {number} Number of represented bits
   */
  get NumBits () {
    if (this.isNegative) {
      return this.eq(UINT64_MIN) ? 64 : this.neg().NumBits
    }

    let val = (this.high !== 0) ? this.high : this.low

    let bit
    for (bit = 31; bit > 0; bit--) {
      if ((val & (1 << bit)) !== 0) {
        break
      }
    }

    return (this.high !== 0) ? (bit + 33) : (bit + 1)
  }

  /**
   * Is this value zero?
   * @this {!UInt64}
   * @returns {boolean} Zero test result
   */
  get isZero () {
    return (this.high === 0) && (this.low === 0)
  }

  /**
   * Is this value positive?
   * @this {!UInt64}
   * @returns {boolean} Positive test result
   */
  get isPositive () {
    return true
  }

  /**
   * Is this value negative?
   * @this {!UInt64}
   * @returns {boolean} Negative test result
   */
  get isNegative () {
    return false
  }

  /**
   * Is this value odd?
   * @this {!UInt64}
   * @returns {boolean} Odd test result
   */
  get isOdd () {
    return (this.low & 1) === 1
  }

  /**
   * Is this value even?
   * @this {!UInt64}
   * @returns {boolean} Even test result
   */
  get isEven () {
    return (this.low & 1) === 0
  }

  /**
   * Is this value equal to other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Equal result
   */
  eq (other) {
    if (Int64.isInt64(other)) {
      if (((this.high >>> 31) === 1) && ((other.high >>> 31) === 1)) {
        return false
      }
    } else if (!UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return (this.high === other.high) && (this.low === other.low)
  }

  /**
   * Is this value not equal to other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Not equal result
   */
  ne (other) {
    return !this.eq(other)
  }

  /**
   * Is this value less than other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Less than result
   */
  lt (other) {
    return this.cmp(other) < 0
  }

  /**
   * Is this value less than or equal other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Less than or equal result
   */
  lte (other) {
    return this.cmp(other) <= 0
  }

  /**
   * Is this value greater than other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Greater than result
   */
  gt (other) {
    return this.cmp(other) > 0
  }

  /**
   * Is this value greater than or equal other one?
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {boolean} Greater than or equal result
   */
  gte (other) {
    return this.cmp(other) >= 0
  }

  /**
   * Compare this value to other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {number} 0 if they are the same, 1 if the this is greater and -1 if the given one is greater
   */
  cmp (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    if (this.eq(other)) {
      return 0
    }
    let thisNeg = this.isNegative
    let otherNeg = other.isNegative
    if (thisNeg && !otherNeg) {
      return -1
    }
    if (!thisNeg && otherNeg) {
      return 1
    }
    // Both are positive if at least one is unsigned
    return (((other.high >>> 0) > (this.high >>> 0)) || ((other.high === this.high) && (other.low >>> 0) > (this.low >>> 0))) ? -1 : 1
  }
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
  /**
   * Negates this value
   * @this {!UInt64}
   * @returns {!UInt64} Negated value
   */
  neg () {
    return this.not().add(UINT64_ONE)
  }

  /**
   * Add this value to other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Sum of two values
   */
  add (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }

    // Divide each number into 4 chunks of 16 bits, and then sum the chunks

    let a48 = this.high >>> 16
    let a32 = this.high & 0xFFFF
    let a16 = this.low >>> 16
    let a00 = this.low & 0xFFFF

    let b48 = other.high >>> 16
    let b32 = other.high & 0xFFFF
    let b16 = other.low >>> 16
    let b00 = other.low & 0xFFFF

    let c48 = 0
    let c32 = 0
    let c16 = 0
    let c00 = 0
    c00 += a00 + b00
    c16 += c00 >>> 16
    c00 &= 0xFFFF
    c16 += a16 + b16
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c32 += a32 + b32
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c48 += a48 + b48
    c48 &= 0xFFFF
    return UInt64.fromBits((c16 << 16) | c00, (c48 << 16) | c32)
  }

  /**
   * Subtract other value from this value
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Difference of two values
   */
  sub (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return this.add(other.neg())
  }

  /**
   * Multiply this value with other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Multiplication of two values
   */
  mul (other) {
    if (this.isZero) {
      return UINT64_ZERO
    }
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }

    if (other.isZero) {
      return UINT64_ZERO
    }
    if (this.eq(UINT64_MIN)) {
      return other.isOdd ? UINT64_MIN : UINT64_ZERO
    }
    if (other.eq(UINT64_MIN)) {
      return this.isOdd ? UINT64_MIN : UINT64_ZERO
    }

    if (this.isNegative) {
      if (other.isNegative) {
        return this.neg().mul(other.neg())
      } else {
        return this.neg().mul(other).neg()
      }
    } else if (other.isNegative) {
      return this.mul(other.neg()).neg()
    }

    // If both longs are small, use float multiplication
    if (this.lt(UINT64_TWO_PWR_24) && other.lt(UINT64_TWO_PWR_24)) {
      return UInt64.fromNumber(this.toNumber() * other.toNumber())
    }

    // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
    // We can skip products that would overflow.

    let a48 = this.high >>> 16
    let a32 = this.high & 0xFFFF
    let a16 = this.low >>> 16
    let a00 = this.low & 0xFFFF

    let b48 = other.high >>> 16
    let b32 = other.high & 0xFFFF
    let b16 = other.low >>> 16
    let b00 = other.low & 0xFFFF

    let c48 = 0
    let c32 = 0
    let c16 = 0
    let c00 = 0
    c00 += a00 * b00
    c16 += c00 >>> 16
    c00 &= 0xFFFF
    c16 += a16 * b00
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c16 += a00 * b16
    c32 += c16 >>> 16
    c16 &= 0xFFFF
    c32 += a32 * b00
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c32 += a16 * b16
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c32 += a00 * b32
    c48 += c32 >>> 16
    c32 &= 0xFFFF
    c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48
    c48 &= 0xFFFF
    return UInt64.fromBits((c16 << 16) | c00, (c48 << 16) | c32)
  }

  /**
   * Divide this value by other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Division of two values
   */
  div (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    if (other.isZero) {
      throw new Error('Division by zero!')
    }

    if (this.isZero) {
      return UINT64_ZERO
    }

    let approx, rem, res

    // The algorithm below has not been made for unsigned longs. It's therefore required to take special care of
    // the MSB prior to running it.
    if (Int64.isInt64(other)) {
      other = other.toUnsigned()
    }
    if (other.gt(this)) {
      return UINT64_ZERO
    }
    // 15 >>> 1 = 7 ; with divisor = 8 ; true
    if (other.gt(this.shru(1))) {
      return UINT64_ONE
    }
    res = UINT64_ZERO

    // Repeat the following until the remainder is less than other:  find a floating-point that approximates
    // remainder / other *from below*, add this into the result, and subtract it from the remainder.
    // It is critical that the approximate value is less than or equal to the real value so that the
    // remainder never becomes negative.
    rem = this
    while (rem.gte(other)) {
      // Approximate the result of division. This may be a little greater or smaller than the actual value
      approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()))

      // We will tweak the approximate result by changing it in the 48-th digit or the smallest non-fractional
      // digit, whichever is larger.
      let log2 = Math.ceil(Math.log(approx) / Math.LN2)
      let delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48)

      // Decrease the approximation until it is smaller than the remainder.
      // Note that if it is too large, the product overflows and is negative.
      let approxRes = Int64.fromNumber(approx)
      let approxRem = approxRes.mul(other)
      while (approxRem.isNegative || approxRem.gt(rem)) {
        approx -= delta
        approxRes = UInt64.fromNumber(approx)
        approxRem = approxRes.mul(other)
      }

      // We know the answer can't be zero... and actually, zero would cause infinite recursion since
      // we would make no progress.
      if (approxRes.isZero) {
        approxRes = UINT64_ONE
      }

      res = res.add(approxRes)
      rem = rem.sub(approxRem)
    }
    return res
  }

  /**
   * Modulo this value by other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Module of two values
   */
  mod (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return this.sub(this.div(other).mul(other))
  }

  /**
   * Bitwise NOT this value
   * @this {!UInt64}
   * @returns {!UInt64} Bitwise NOT result value
   */
  not () {
    return UInt64.fromBits(~this.low, ~this.high)
  }

  /**
   * Bitwise AND this value and other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Bitwise AND result value
   */
  and (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return UInt64.fromBits(this.low & other.low, this.high & other.high)
  }

  /**
   * Bitwise OR this value and other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Bitwise OR result value
   */
  or (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return UInt64.fromBits(this.low | other.low, this.high | other.high)
  }

  /**
   * Bitwise XOR this value and other one
   * @this {!UInt64}
   * @param {!Int64|!UInt64|number|string} other Other value
   * @returns {!UInt64} Bitwise XOR result value
   */
  xor (other) {
    if (!Int64.isInt64(other) && !UInt64.isUInt64(other)) {
      other = UInt64.fromValue(other)
    }
    return UInt64.fromBits(this.low ^ other.low, this.high ^ other.high)
  }

  /**
   * This UInt64 with bits shifted to the left by the given amount
   * @this {!UInt64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!UInt64} Shifted result value
   */
  shl (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    if ((numBits &= 63) === 0) {
      return this
    } else if (numBits < 32) {
      return UInt64.fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)))
    } else {
      return UInt64.fromBits(0, this.low << (numBits - 32))
    }
  }

  /**
   * This UInt64 with bits shifted to the right by the given amount
   * @this {!UInt64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!UInt64} Shifted result value
   */
  shr (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    if ((numBits &= 63) === 0) {
      return this
    } else if (numBits < 32) {
      return UInt64.fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits)
    } else {
      return UInt64.fromBits(this.high >> (numBits - 32), (this.high >= 0) ? 0 : -1)
    }
  }

  /**
   * This UInt64 with bits shifted to the right by the given amount with filling zeros to the left
   * @this {!UInt64}
   * @param {Int64|UInt64|number} numBits Number of bits
   * @returns {!UInt64} Shifted result value
   */
  shru (numBits) {
    if (Int64.isInt64(numBits) || UInt64.isUInt64(numBits)) {
      numBits = numBits.toInt32()
    }
    numBits &= 63
    if (numBits === 0) {
      return this
    } else {
      let high = this.high
      if (numBits < 32) {
        let low = this.low
        return UInt64.fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits)
      } else if (numBits === 32) {
        return UInt64.fromBits(high, 0)
      } else {
        return UInt64.fromBits(high >>> (numBits - 32), 0)
      }
    }
  }
}

Object.defineProperty(UInt64.prototype, '__isUInt64__', { value: true })

// Useful constants
const UINT64_TWO_PWR_16_DBL = 1 << 16
const UINT64_TWO_PWR_24_DBL = 1 << 24
const UINT64_TWO_PWR_32_DBL = UINT64_TWO_PWR_16_DBL * UINT64_TWO_PWR_16_DBL
const UINT64_TWO_PWR_64_DBL = UINT64_TWO_PWR_32_DBL * UINT64_TWO_PWR_32_DBL

// UInt64 values cache
const UInt64Cache = {}

// Int64 values constants
const UINT64_ZERO = UInt64.fromInt32(0)
const UINT64_ONE = UInt64.fromInt32(1)
const UINT64_MIN = UInt64.fromInt32(0)
const UINT64_MAX = UInt64.fromBits(0xFFFFFFFF | 0, 0xFFFFFFFF | 0)
const UINT64_TWO_PWR_24 = UInt64.fromInt32(UINT64_TWO_PWR_24_DBL)

exports.UInt64 = UInt64
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateUUID(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "uuid.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable prefer-const,no-loss-of-precision */
'use strict'

/**
 * Universally unique identifier (UUID)
 * A universally unique identifier (UUID) is an identifier standard used
 * in software construction. This implementation generates the following
 * UUID types:
 * - Nil UUID0 (all bits set to zero)
 * - Sequential UUID1 (time based version)
 * - Random UUID4 (randomly or pseudo-randomly generated version)
 *
 * A UUID is simply a 128-bit value: "123e4567-e89b-12d3-a456-426655440000"
 *
 * https://en.wikipedia.org/wiki/Universally_unique_identifier
 * https://www.ietf.org/rfc/rfc4122.txt
 */
class UUID {
  /**
   * Initialize a new UUID instance from the provided source or create nil UUID if the source is not provided
   * @param {string|Uint8Array|UUID} value Source value
   * @constructor
   */
  constructor (value = undefined) {
    if (value == null) {
      this.data = new Uint8Array(16)
    } else if ((value instanceof Array) || (value instanceof Uint8Array)) {
      this.data = UUID.fromBytes(value).data.slice()
    } else if ((typeof value === 'string') || (value instanceof String)) {
      this.data = UUID.fromString(value).data.slice()
    } else if (value instanceof UUID) {
      this.data = value.data.slice()
    } else {
      throw new Error('Unsupported UUID base type ' + (typeof value))
    }
  }

  /**
   * Is the specified object UUID?
   * @param obj Object to check
   * @returns {!boolean}
   */
  static isUUID (obj) {
    return (obj && obj.__isUUID__) === true
  }

  /**
   * Returns UUID from its bytes representation
   * @param {!Array.<number>|!Uint8Array} bytes Byte representation
   * @returns {!UUID} The corresponding UUID value
   */
  static fromBytes (bytes) {
    let result = new UUID()
    for (let i = 0; i < bytes.length; i++) {
      result.data[i] = bytes[i] & 0xFF
    }
    return result
  }

  /**
   * Returns UUID representing using its separate parts
   * @param {number} timeLow The low field of the timestamp (32-bit)
   * @param {number} timeMid The middle field of the timestamp (16-bit)
   * @param {number} timeHiAndVersion The high field of the timestamp multiplexed with the version number (16-bit)
   * @param {number} clockSeqHiAndReserved The high field of the clock sequence multiplexed with the variant (8-bit)
   * @param {number} clockSeqLow The low field of the clock sequence (8-bit)
   * @param {number} nodeLow The low field of the spatially unique node identifier (32-bit)
   * @param {number} nodeMid The middle field of the spatially unique node identifier (8-bit)
   * @param {number} nodeHi The high field of the spatially unique node identifier (8-bit)
   * @returns {!UUID} The corresponding UUID value
   */
  static fromParts (timeLow, timeMid, timeHiAndVersion, clockSeqHiAndReserved, clockSeqLow, nodeLow, nodeMid, nodeHi) {
    let result = new UUID()
    result.data[0] = (timeLow >>> 24)
    result.data[1] = (timeLow >>> 16)
    result.data[2] = (timeLow >>> 8)
    result.data[3] = (timeLow & 0xFF)
    result.data[4] = (timeMid >>> 8)
    result.data[5] = (timeMid & 0xFF)
    result.data[6] = (timeHiAndVersion >>> 8)
    result.data[7] = (timeHiAndVersion & 0xFF)
    result.data[8] = (clockSeqHiAndReserved & 0xFF)
    result.data[9] = (clockSeqLow & 0xFF)
    result.data[10] = (nodeLow >>> 24)
    result.data[11] = (nodeLow >>> 16)
    result.data[12] = (nodeLow >>> 8)
    result.data[13] = (nodeLow & 0xFF)
    result.data[14] = (nodeMid & 0xFF)
    result.data[15] = (nodeHi & 0xFF)
    return result
  }

  /**
   * Returns UUID representation of the given string
   * @param {string} str The textual representation of the UUID
   * @returns {!UUID} The corresponding UUID value
   */
  static fromString (str) {
    if (str.length === 0) {
      throw new Error('Empty string!')
    }
    let result = new UUID()
    str = str.replace(/[{}-]/g, '')
    for (let i = 0; i < str.length; i++) {
      result.data[i] = parseInt(str.substr(i * 2, 2), 16)
    }
    return result
  }

  /**
   * Generate nil UUID0 (all bits set to zero)
   * @returns {!UUID}
   */
  static nil () {
    return new UUID()
  }

  /**
   * Generate sequential UUID1 (time based version)
   * @returns {!UUID}
   */
  static sequential () {
    let now = new Date().getTime()
    let sequence = Math.floor(Math.random() * TWO_POW_14)
    let nodeHi = Math.floor(Math.random() * TWO_POW_8)
    let nodeMid = Math.floor(Math.random() * TWO_POW_8)
    let nodeLow = Math.floor(Math.random() * TWO_POW_32)
    let tick = Math.floor(Math.random() * TWO_POW_4)
    let timestamp = 0
    let timestampRatio = 1 / 4

    if (now !== timestamp) {
      if (now < timestamp) {
        sequence++
      }
      timestamp = now
      tick = Math.floor(Math.random() * TWO_POW_4)
    } else if ((Math.random() < timestampRatio) && (tick < 9984)) {
      tick += 1 + Math.floor(Math.random() * TWO_POW_4)
    } else {
      sequence++
    }

    let ts = timestamp - Date.UTC(1582, 9, 15)
    let hm = ((ts / 0x100000000) * 10000) & 0xFFFFFFF
    let tl = ((ts & 0xFFFFFFF) * 10000) % 0x100000000 + tick
    let thav = (hm >>> 16 & 0xFFF) | 0x1000

    sequence &= 0x3FFF
    let cshar = (sequence >>> 8) | 0x80
    let csl = sequence & 0xFF

    return UUID.fromParts(tl, hm & 0xFFFF, thav, cshar, csl, nodeLow, nodeMid, nodeHi)
  }

  /**
   * Generate random UUID4 (randomly or pseudo-randomly generated version)
   * @returns {!UUID}
   */
  static random () {
    return UUID.fromParts(
      Math.floor(Math.random() * TWO_POW_32),
      Math.floor(Math.random() * TWO_POW_16),
      0x4000 | Math.floor(Math.random() * TWO_POW_12),
      0x80 | Math.floor(Math.random() * TWO_POW_6),
      Math.floor(Math.random() * TWO_POW_8),
      Math.floor(Math.random() * TWO_POW_32),
      Math.floor(Math.random() * TWO_POW_8),
      Math.floor(Math.random() * TWO_POW_8)
    )
  }

  /**
   * Converts the UUID to its bytes representation
   * @this {!UUID}
   * @returns {!Array.<number>} Bytes representation
   */
  toBytes () {
    let result = []
    for (let i = 0; i < this.data.length; i++) {
      result.push(this.data[i])
    }
    return result
  }

  /**
   * Converts the UUID to string representation in format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
   * @this {!UUID}
   * @returns {string} Result string
   */
  toString () {
    let result = ''
    for (let i = 0; i < this.data.length; i++) {
      result += (this.data[i] >>> 4).toString(16)
      result += (this.data[i] & 0xF).toString(16)
      if ((i === 3) || (i === 5) || (i === 7) || (i === 9)) {
        result += '-'
      }
    }
    return result
  }

  /**
   * Is this value equal to other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Equal result
   */
  eq (other) {
    if (!UUID.isUUID(other)) {
      return false
    }
    for (let i = 0; i < this.data.length; i++) {
      if (this.data[i] !== other.data[i]) {
        return false
      }
    }
    return true
  }

  /**
   * Is this value not equal to other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Not equal result
   */
  ne (other) {
    return !this.eq(other)
  }

  /**
   * Is this value less than other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Less than result
   */
  lt (other) {
    return this.cmp(other) < 0
  }

  /**
   * Is this value less than or equal other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Less than or equal result
   */
  lte (other) {
    return this.cmp(other) <= 0
  }

  /**
   * Is this value greater than other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Greater than result
   */
  gt (other) {
    return this.cmp(other) > 0
  }

  /**
   * Is this value greater than or equal other one?
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {boolean} Greater than or equal result
   */
  gte (other) {
    return this.cmp(other) >= 0
  }

  /**
   * Compare this value to other one
   * @this {UUID}
   * @param {UUID} other Other value
   * @returns {number} 0 if they are the same, 1 if the this is greater and -1 if the given one is greater
   */
  cmp (other) {
    if (!UUID.isUUID(other)) {
      return 1
    }
    for (let i = 0; i < this.data.length; i++) {
      let diff = this.data[i] - other.data[i]
      if (diff !== 0) {
        return diff
      }
    }
    return 0
  }
}

Object.defineProperty(UUID.prototype, '__isUUID__', { value: true })

// UUID internal constants
const TWO_POW_32 = 4294967296
const TWO_POW_16 = 65536
const TWO_POW_14 = 16384
const TWO_POW_12 = 4096
const TWO_POW_8 = 256
const TWO_POW_6 = 64
const TWO_POW_4 = 16

exports.UUID = UUID
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateIEEE754(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "ieee754.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable prefer-const,no-loss-of-precision */
'use strict'

/**
 * Read float/double value from the given buffer
 * @param {!Buffer|!Uint8Array} buffer Buffer to read
 * @param {!number} offset Buffer offset
 * @param {!boolean} isLE Little endian flag
 * @param {!number} mLen Value length (float = 23; double = 52)
 * @param {!number} nBytes Value size in bytes (float = 4; double = 8)
 * @returns {!number} Float/Double value
 */
let ieee754read = function (buffer, offset, isLE, mLen, nBytes) {
  let e, m
  let eLen = (nBytes * 8) - mLen - 1
  let eMax = (1 << eLen) - 1
  let eBias = eMax >> 1
  let nBits = -7
  let i = isLE ? (nBytes - 1) : 0
  let d = isLE ? -1 : 1
  let s = buffer[offset + i]

  i += d

  e = s & ((1 << (-nBits)) - 1)
  s >>= (-nBits)
  nBits += eLen
  for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8);

  m = e & ((1 << (-nBits)) - 1)
  e >>= (-nBits)
  nBits += mLen
  for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8);

  if (e === 0) {
    e = 1 - eBias
  } else if (e === eMax) {
    return m ? NaN : ((s ? -1 : 1) * Infinity)
  } else {
    m = m + Math.pow(2, mLen)
    e = e - eBias
  }
  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
}

exports.ieee754read = ieee754read

/**
 * Write float/double value into the given buffer
 * @param {!Buffer|!Uint8Array} buffer Buffer to write
 * @param {!number} offset Buffer offset
 * @param {!number} value Value to write
 * @param {!boolean} isLE Little endian flag
 * @param {!number} mLen Value length (float = 23; double = 52)
 * @param {!number} nBytes Value size in bytes (float = 4; double = 8)
 */
let ieee754write = function (buffer, offset, value, isLE, mLen, nBytes) {
  let e, m, c
  let eLen = (nBytes * 8) - mLen - 1
  let eMax = (1 << eLen) - 1
  let eBias = eMax >> 1
  let rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
  let i = isLE ? 0 : (nBytes - 1)
  let d = isLE ? 1 : -1
  let s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0

  value = Math.abs(value)

  if (isNaN(value) || value === Infinity) {
    m = isNaN(value) ? 1 : 0
    e = eMax
  } else {
    e = Math.floor(Math.log(value) / Math.LN2)
    if (value * (c = Math.pow(2, -e)) < 1) {
      e--
      c *= 2
    }
    if (e + eBias >= 1) {
      value += rt / c
    } else {
      value += rt * Math.pow(2, 1 - eBias)
    }
    if (value * c >= 2) {
      e++
      c /= 2
    }

    if (e + eBias >= eMax) {
      m = 0
      e = eMax
    } else if (e + eBias >= 1) {
      m = ((value * c) - 1) * Math.pow(2, mLen)
      e = e + eBias
    } else {
      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
      e = 0
    }
  }

  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8);

  e = (e << mLen) | m
  eLen += mLen
  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8);

  buffer[offset + i - d] |= s * 128
}

exports.ieee754write = ieee754write
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateUTF8(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "utf8.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable prefer-const,no-loss-of-precision */
'use strict'

/**
 * Get bytes count required for encode string to UTF-8 bytes array
 * @param {!string} str String value to encode
 * @return {!number} Bytes count
 */
let utf8count = function (str) {
  let bytes = 0
  for (let i = 0; i < str.length; i++) {
    let codePoint = str.charCodeAt(i)
    if (codePoint < 0x80) {
      bytes++
    } else if ((codePoint > 0x7F) && (codePoint < 0x800)) {
      bytes = bytes + 2
    } else {
      bytes = bytes + 3
    }
  }
  return bytes
}

exports.utf8count = utf8count

/**
 * Encode string to UTF-8 bytes array
 * @param {!Uint8Array} buffer Bytes array to encode
 * @param {!number} offset Offset in bytes array
 * @param {!string} str String value to encode
 */
let utf8encode = function (buffer, offset, str) {
  let units = Infinity
  let codePoint
  let index = offset
  let length = str.length
  let leadSurrogate = null

  for (let i = 0; i < length; ++i) {
    codePoint = str.charCodeAt(i)

    // Is surrogate component...
    if ((codePoint > 0xD7FF) && (codePoint < 0xE000)) {
      // Last char was a lead
      if (!leadSurrogate) {
        // No lead yet
        if (codePoint > 0xDBFF) {
          // Unexpected trail
          if ((units -= 3) > -1) {
            buffer[index++] = 0xEF
            buffer[index++] = 0xBF
            buffer[index++] = 0xBD
          }
          continue
        } else if ((i + 1) === length) {
          // Unpaired lead
          if ((units -= 3) > -1) {
            buffer[index++] = 0xEF
            buffer[index++] = 0xBF
            buffer[index++] = 0xBD
          }
          continue
        }

        // Valid lead
        leadSurrogate = codePoint

        continue
      }

      // 2 leads in a row
      if (codePoint < 0xDC00) {
        if ((units -= 3) > -1) {
          buffer[index++] = 0xEF
          buffer[index++] = 0xBF
          buffer[index++] = 0xBD
        }
        leadSurrogate = codePoint
        continue
      }

      // Valid surrogate pair
      codePoint = (((leadSurrogate - 0xD800) << 10) | (codePoint - 0xDC00)) + 0x10000
    } else if (leadSurrogate) {
      // Valid bmp char, but last char was a lead
      if ((units -= 3) > -1) {
        buffer[index++] = 0xEF
        buffer[index++] = 0xBF
        buffer[index++] = 0xBD
      }
    }

    leadSurrogate = null

    // Encode utf8
    if (codePoint < 0x80) {
      if ((units -= 1) < 0) {
        break
      }
      buffer[index++] = codePoint
    } else if (codePoint < 0x800) {
      if ((units -= 2) < 0) {
        break
      }
      buffer[index++] = codePoint >> 0x6 | 0xC0
      buffer[index++] = codePoint & 0x3F | 0x80
    } else if (codePoint < 0x10000) {
      if ((units -= 3) < 0) {
        break
      }
      buffer[index++] = codePoint >> 0xC | 0xE0
      buffer[index++] = codePoint >> 0x6 & 0x3F | 0x80
      buffer[index++] = codePoint & 0x3F | 0x80
    } else if (codePoint < 0x110000) {
      if ((units -= 4) < 0) {
        break
      }
      buffer[index++] = codePoint >> 0x12 | 0xF0
      buffer[index++] = codePoint >> 0xC & 0x3F | 0x80
      buffer[index++] = codePoint >> 0x6 & 0x3F | 0x80
      buffer[index++] = codePoint & 0x3F | 0x80
    } else {
      throw new Error('Invalid code point!')
    }
  }
}

exports.utf8encode = utf8encode

/**
 * Decode UTF-8 bytes array to string
 * @param {!Uint8Array} buffer UTF-8 bytes array to decode
 * @param {!number} offset Offset in UTF-8 bytes array
 * @param {!number} size UTF-8 bytes array size
 * @return {!string} String value
 */
let utf8decode = function (buffer, offset, size) {
  let start = offset
  let end = offset + size
  let res = []

  let i = start
  while (i < end) {
    let firstByte = buffer[i]
    let codePoint = null
    let bytesPerSequence = (firstByte > 0xEF) ? 4 : ((firstByte > 0xDF) ? 3 : ((firstByte > 0xBF) ? 2 : 1))

    if ((i + bytesPerSequence) <= end) {
      let secondByte
      let thirdByte
      let fourthByte
      let tempCodePoint

      switch (bytesPerSequence) {
        case 1:
          if (firstByte < 0x80) {
            codePoint = firstByte
          }
          break
        case 2:
          secondByte = buffer[i + 1]
          if ((secondByte & 0xC0) === 0x80) {
            tempCodePoint = ((firstByte & 0x1F) << 0x6) | (secondByte & 0x3F)
            if (tempCodePoint > 0x7F) {
              codePoint = tempCodePoint
            }
          }
          break
        case 3:
          secondByte = buffer[i + 1]
          thirdByte = buffer[i + 2]
          if (((secondByte & 0xC0) === 0x80) && ((thirdByte & 0xC0) === 0x80)) {
            tempCodePoint = ((firstByte & 0xF) << 0xC) | ((secondByte & 0x3F) << 0x6) | (thirdByte & 0x3F)
            if ((tempCodePoint > 0x7FF) && ((tempCodePoint < 0xD800) || (tempCodePoint > 0xDFFF))) {
              codePoint = tempCodePoint
            }
          }
          break
        case 4:
          secondByte = buffer[i + 1]
          thirdByte = buffer[i + 2]
          fourthByte = buffer[i + 3]
          if (((secondByte & 0xC0) === 0x80) && ((thirdByte & 0xC0) === 0x80) && ((fourthByte & 0xC0) === 0x80)) {
            tempCodePoint = ((firstByte & 0xF) << 0x12) | ((secondByte & 0x3F) << 0xC) | ((thirdByte & 0x3F) << 0x6) | (fourthByte & 0x3F)
            if ((tempCodePoint > 0xFFFF) && (tempCodePoint < 0x110000)) {
              codePoint = tempCodePoint
            }
          }
      }
    }

    if (codePoint === null) {
      // We did not generate a valid codePoint so insert a replacement char (U+FFFD) and advance only 1 byte
      codePoint = 0xFFFD
      bytesPerSequence = 1
    } else if (codePoint > 0xFFFF) {
      // Encode to utf16 (surrogate pair dance)
      codePoint -= 0x10000
      res.push((codePoint >>> 10) & 0x3FF | 0xD800)
      codePoint = 0xDC00 | codePoint & 0x3FF
    }

    res.push(codePoint)
    i += bytesPerSequence
  }

  // Based on http://stackoverflow.com/a/22747272/680742, the browser with the lowest limit is Chrome, with 0x10000 args.
  // We go 1 magnitude less, for safety
  const MAX_ARGUMENTS_LENGTH = 0x1000

  if (res.length <= MAX_ARGUMENTS_LENGTH) {
    // Avoid extra slice()
    return String.fromCharCode.apply(String, res)
  }

  let result = ''

  // Decode in chunks to avoid "call stack size exceeded"
  i = 0
  while (i < res.length) {
    result += String.fromCharCode.apply(String, res.slice(i, i += MAX_ARGUMENTS_LENGTH))
  }

  return result
}

exports.utf8decode = utf8decode
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateFBE(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "fbe.js";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
/* eslint-disable prefer-const,no-loss-of-precision */
'use strict'

const big = require('./big')
const int64 = require('./int64')
const ieee754 = require('./ieee754')
const utf8 = require('./utf8')
const uuid = require('./uuid')

const Big = big.Big
const Int64 = int64.Int64
const UInt64 = int64.UInt64
const UUID = uuid.UUID
const ieee754read = ieee754.ieee754read
const ieee754write = ieee754.ieee754write
const utf8count = utf8.utf8count
const utf8encode = utf8.utf8encode
const utf8decode = utf8.utf8decode
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common models
    GenerateFBEDeferredPromise();
    GenerateFBEBaseBuffer();
    GenerateFBEWriteBuffer();
    GenerateFBEReadBuffer();
    GenerateFBEModel();
    GenerateFBEFieldModelBase();
    GenerateFBEFieldModel();
    GenerateFBEFieldModel("Bool", "bool", "boolean", "1", "false");
    GenerateFBEFieldModel("Byte", "byte", "number", "1", "0");
    GenerateFBEFieldModel("Char", "char", "string", "1", "'\\0'");
    GenerateFBEFieldModel("WChar", "wchar", "string", "4", "'\\0'");
    GenerateFBEFieldModel("Int8", "int8", "number", "1", "0");
    GenerateFBEFieldModel("UInt8", "uint8", "number", "1", "0");
    GenerateFBEFieldModel("Int16", "int16", "number", "2", "0");
    GenerateFBEFieldModel("UInt16", "uint16", "number", "2", "0");
    GenerateFBEFieldModel("Int32", "int32", "number", "4", "0");
    GenerateFBEFieldModel("UInt32", "uint32", "number", "4", "0");
    GenerateFBEFieldModel("Int64", "int64", "Int64", "8", "new Int64(0, 0)");
    GenerateFBEFieldModel("UInt64", "uint64", "UInt64", "8", "new UInt64(0, 0)");
    GenerateFBEFieldModel("Float", "float", "number", "4", "0.0");
    GenerateFBEFieldModel("Double", "double", "number", "8", "0.0");
    GenerateFBEFieldModelDecimal();
    GenerateFBEFieldModelTimestamp();
    GenerateFBEFieldModelUUID();
    GenerateFBEFieldModelBytes();
    GenerateFBEFieldModelString();
    GenerateFBEFieldModelOptional();
    GenerateFBEFieldModelArray();
    GenerateFBEFieldModelVector();
    GenerateFBEFieldModelSet();
    GenerateFBEFieldModelMap();
    if (Final())
    {
        GenerateFBEFinalModel();
        GenerateFBEFinalModel("Bool", "bool", "boolean", "1", "false");
        GenerateFBEFinalModel("Byte", "byte", "number", "1", "0");
        GenerateFBEFinalModel("Char", "char", "string", "1", "'\\0'");
        GenerateFBEFinalModel("WChar", "wchar", "string", "4", "'\\0'");
        GenerateFBEFinalModel("Int8", "int8", "number", "1", "0");
        GenerateFBEFinalModel("UInt8", "uint8", "number", "1", "0");
        GenerateFBEFinalModel("Int16", "int16", "number", "2", "0");
        GenerateFBEFinalModel("UInt16", "uint16", "number", "2", "0");
        GenerateFBEFinalModel("Int32", "int32", "number", "4", "0");
        GenerateFBEFinalModel("UInt32", "uint32", "number", "4", "0");
        GenerateFBEFinalModel("Int64", "int64", "Int64", "8", "new Int64(0, 0)");
        GenerateFBEFinalModel("UInt64", "uint64", "UInt64", "8", "new UInt64(0, 0)");
        GenerateFBEFinalModel("Float", "float", "number", "4", "0.0");
        GenerateFBEFinalModel("Double", "double", "number", "8", "0.0");
        GenerateFBEFinalModelDecimal();
        GenerateFBEFinalModelTimestamp();
        GenerateFBEFinalModelUUID();
        GenerateFBEFinalModelBytes();
        GenerateFBEFinalModelString();
        GenerateFBEFinalModelOptional();
        GenerateFBEFinalModelArray();
        GenerateFBEFinalModelVector();
        GenerateFBEFinalModelSet();
        GenerateFBEFinalModelMap();
    }
    if (Proto())
    {
        GenerateFBESender();
        GenerateFBEReceiver();
        GenerateFBEClient();
    }
    if (JSON())
        GenerateFBEJson();

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorJavaScript::GenerateFBEDeferredPromise()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding deferred promise
 */
class DeferredPromise {
  /**
   * Initialize buffer
   * @constructor
   */
  constructor () {
    this._promise = new Promise((resolve, reject) => {
      // Assign the resolve and reject functions to `this` making them usable on the class instance
      this.resolve = resolve
      this.reject = reject
    })
    // Bind `then` and `catch` to implement the same interface as Promise
    this.then = this._promise.then.bind(this._promise)
    this.catch = this._promise.catch.bind(this._promise)
    this[Symbol.toStringTag] = 'Promise'
  }
}

exports.DeferredPromise = DeferredPromise
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEBaseBuffer()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base buffer
 */
class BaseBuffer {
  /**
   * Initialize buffer
   * @constructor
   */
  constructor () {
    this._buffer = null
    this._size = 0
    this._offset = 0
  }

  /**
   * Is the buffer empty?
   * @this {!BaseBuffer}
   * @returns {boolean} Buffer empty flag
   */
  get isEmpty () {
    return (this._buffer == null) || (this._size === 0)
  }

  /**
   * Get the buffer
   * @this {!BaseBuffer}
   * @returns {Uint8Array} Buffer
   */
  get buffer () {
    return this._buffer
  }

  /**
   * Get the buffer capacity
   * @this {!BaseBuffer}
   * @returns {number} Buffer capacity
   */
  get capacity () {
    return this._buffer.length
  }

  /**
   * Get the buffer length
   * @this {!BaseBuffer}
   * @returns {number} Buffer length
   */
  get length () {
    return this._size
  }

  /**
   * Get the buffer size
   * @this {!BaseBuffer}
   * @returns {number} Buffer size
   */
  get size () {
    return this._size
  }

  /**
   * Get the buffer offset
   * @this {!BaseBuffer}
   * @returns {number} Buffer offset
   */
  get offset () {
    return this._offset
  }

  /**
   * Shift the current buffer offset
   * @this {!BaseBuffer}
   * @param {!number} offset Offset
   */
  shift (offset) {
    this._offset += offset
  }

  /**
   * Unshift the current buffer offset
   * @this {!BaseBuffer}
   * @param {!number} offset Offset
   */
  unshift (offset) {
    this._offset -= offset
  }

  /**
   * Check the buffer offset bounds
   * @this {!BaseBuffer}
   * @param {!number} offset Offset
   * @param {!number} size Size
   */
  checkOffset (offset, size) {
    if (((offset % 1) !== 0) || (offset < 0)) {
      throw new RangeError('Invalid offset!')
    }
    if ((offset + size) > this.length) {
      throw new RangeError('Out of bounds!')
    }
  }

  /**
   * Check the value range and its buffer offset bounds
   * @this {!BaseBuffer}
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @param {!number} value Value
   * @param {!number} min Min value
   * @param {!number} max Max value
   */
  checkValue (offset, size, value, min, max) {
    this.checkOffset(offset, size)
    if ((value < min) || (value > max)) {
      throw new RangeError('Value is out of bounds!')
    }
  }
}

exports.BaseBuffer = BaseBuffer
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEWriteBuffer()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding write buffer based on the dynamic byte array
 */
class WriteBuffer extends BaseBuffer {
  /**
   * Initialize write buffer with the given capacity
   * @param {number=} capacity Write buffer capacity, defaults is 0
   * @constructor
   */
  constructor (capacity = 0) {
    super()
    this._buffer = new Uint8Array(capacity)
    this._size = 0
    this._offset = 0
  }

  /**
   * Attach an empty memory buffer
   * @this {!WriteBuffer}
   */
  attachNew () {
    this._buffer = new Uint8Array(0)
    this._size = 0
    this._offset = 0
  }

  /**
   * Attach an empty memory buffer with a given capacity
   * @this {!WriteBuffer}
   * @param {number=} capacity Write buffer capacity, defaults is 0
   */
  attachCapacity (capacity = 0) {
    this._buffer = new Uint8Array(capacity)
    this._size = 0
    this._offset = 0
  }

  /**
   * Attach a given memory buffer
   * @this {!WriteBuffer}
   * @param {!Uint8Array|!ReadBuffer|!WriteBuffer} buffer Buffer to attach
   * @param {number=} offset Buffer offset, defaults is 0
   * @param {number=} size Buffer size, defaults is buffer.length
   */
  attachBuffer (buffer, offset = 0, size = undefined) {
    if (size == null) {
      size = buffer.length
    }

    if (size <= 0) {
      throw new Error('Invalid size!')
    }
    if (offset > size) {
      throw new Error('Invalid offset!')
    }
    if ((buffer instanceof ReadBuffer) || (buffer instanceof WriteBuffer)) {
      this._buffer = buffer.buffer
    } else {
      this._buffer = buffer
    }
    this._size = size
    this._offset = offset
  }

  /**
   * Allocate memory in the current write buffer and return offset to the allocated memory block
   * @this {!WriteBuffer}
   * @param {!number} size Allocation size
   * @returns {!number} Allocated memory offset
   */
  allocate (size) {
    if (size < 0) {
      throw new Error('Invalid allocation size!')
    }

    let offset = this._size

    // Calculate a new buffer size
    let total = this._size + size

    if (total <= this._buffer.length) {
      this._size = total
      return offset
    }

    let data = new Uint8Array(Math.max(total, 2 * this._buffer.length))
    data.set(this._buffer)
    this._buffer = data
    this._size = total
    return offset
  }

  /**
   * Remove some memory of the given size from the current write buffer
   * @this {!WriteBuffer}
   * @param {!number} offset Removed memory offset
   * @param {!number} size Removed memory size
   */
  remove (offset, size) {
    if ((offset + size) > this._buffer.length) {
      throw new Error('Invalid offset & size!')
    }

    for (let i = 0; i < size; i++) {
      this._buffer[offset + i] = this._buffer[offset + size + i]
    }
    this._size -= size
    if (this._offset >= (offset + size)) {
      this._offset -= size
    } else if (this._offset >= offset) {
      this._offset -= this._offset - offset
      if (this._offset > this._size) {
        this._offset = this._size
      }
    }
  }

  /**
   * Reserve memory of the given capacity in the current write buffer
   * @this {!WriteBuffer}
   * @param {number} capacity Write buffer capacity
   */
  reserve (capacity) {
    if (capacity < 0) {
      throw new Error('Invalid reserve capacity!')
    }

    if (capacity > this._buffer.length) {
      let data = new Uint8Array(Math.max(capacity, 2 * this._buffer.length))
      data.set(this._buffer)
      this._buffer = data
    }
  }

  /**
   * Resize the current write buffer
   * @this {!WriteBuffer}
   * @param {number} size Write buffer size
   */
  resize (size) {
    this.reserve(size)
    this._size = size
    if (this._offset > this._size) {
      this._offset = this._size
    }
  }

  /**
   * Reset the current write buffer and its offset
   * @this {!WriteBuffer}
   */
  reset () {
    this._size = 0
    this._offset = 0
  }
}

exports.WriteBuffer = WriteBuffer
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEReadBuffer()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding read buffer based on the constant byte buffer
 */
class ReadBuffer extends BaseBuffer {
  /**
   * Attach a given memory buffer
   * @this {!ReadBuffer}
   * @param {!Uint8Array|!ReadBuffer|!WriteBuffer} buffer Buffer to attach
   * @param {number=} offset Buffer offset, defaults is 0
   * @param {number=} size Buffer size, defaults is buffer.length
   */
  attachBuffer (buffer, offset = 0, size = undefined) {
    if (size == null) {
      size = buffer.length
    }

    if (size <= 0) {
      throw new Error('Invalid size!')
    }
    if (offset > size) {
      throw new Error('Invalid offset!')
    }
    if ((buffer instanceof ReadBuffer) || (buffer instanceof WriteBuffer)) {
      this._buffer = buffer.buffer
    } else {
      this._buffer = buffer
    }
    this._size = size
    this._offset = offset
  }

  /**
   * Reset the current read buffer and its offset
   * @this {!ReadBuffer}
   */
  reset () {
    this._buffer = null
    this._size = 0
    this._offset = 0
  }
}

exports.ReadBuffer = ReadBuffer
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEModel()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base model
 */
class Model {
  /**
   * Initialize model with the given buffer
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer, defaults is new WriteBuffer()
   * @constructor
   */
  constructor (buffer = new WriteBuffer()) {
    this._buffer = buffer
  }

  /**
   * Get the buffer
   * @this {!Buffer}
   * @returns {Uint8Array} Buffer
   */
  get buffer () {
    return this._buffer
  }

  /**
   * Attach an empty memory buffer
   * @this {!Model}
   */
  attachNew () {
    this._buffer.attachNew()
  }

  /**
   * Attach an empty memory buffer with a given capacity
   * @this {!Model}
   * @param {number=} capacity Write buffer capacity, defaults is 0
   */
  attachCapacity (capacity = 0) {
    this._buffer.attachCapacity(capacity)
  }

  /**
   * Attach a given memory buffer
   * @this {!Model}
   * @param {!Uint8Array|!ReadBuffer|!WriteBuffer} buffer Buffer to attach
   * @param {number=} offset Buffer offset, defaults is 0
   * @param {number=} size Buffer size, defaults is undefined
   */
  attachBuffer (buffer, offset = 0, size = undefined) {
    this._buffer.attachBuffer(buffer, offset, size)
  }

  /**
   * Allocate memory in the current write buffer and return offset to the allocated memory block
   * @this {!Model}
   * @param {!number} size Allocation size
   * @returns {!number} Allocated memory offset
   */
  allocate (size) {
    return this._buffer.allocate(size)
  }

  /**
   * Remove some memory of the given size from the current write buffer
   * @this {!Model}
   * @param {!number} offset Removed memory offset
   * @param {!number} size Removed memory size
   */
  remove (offset, size) {
    this._buffer.remove(offset, size)
  }

  /**
   * Reserve memory of the given capacity in the current write buffer
   * @this {!Model}
   * @param {number} capacity Write buffer capacity, defaults is 0
   */
  reserve (capacity) {
    this._buffer.reserve(capacity)
  }

  /**
   * Resize the current write buffer
   * @this {!Model}
   * @param {number} size Write buffer size
   */
  resize (size) {
    this._buffer.resize(size)
  }

  /**
   * Reset the current write buffer and its offset
   * @this {!Model}
   */
  reset () {
    this._buffer.reset()
  }

  /**
   * Shift the current write buffer offset
   * @this {!Model}
   * @param {!number} offset Offset
   */
  shift (offset) {
    this._buffer.shift(offset)
  }

  /**
   * Unshift the current write buffer offset
   * @this {!Model}
   * @param {!number} offset Offset
   */
  unshift (offset) {
    this._buffer.unshift(offset)
  }

  // Buffer I/O methods

  /**
   * Read UInt32 value from the model buffer
   * @this {!Model}
   * @param {!number} offset Offset
   * @returns {!number} UInt32 value
   */
  readUInt32 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 4)
    return (
      (this._buffer.buffer[offset + 0] << 0) |
      (this._buffer.buffer[offset + 1] << 8) |
      (this._buffer.buffer[offset + 2] << 16)) +
      (this._buffer.buffer[offset + 3] * 0x1000000)
  }

  /**
   * Write UInt32 value into the model buffer
   * @this {!Model}
   * @param {!number} offset Offset
   * @param {!number} value Value
   */
  writeUInt32 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 4, value, 0, 0xFFFFFFFF)
    this._buffer.buffer[offset + 3] = (value >>> 24)
    this._buffer.buffer[offset + 2] = (value >>> 16)
    this._buffer.buffer[offset + 1] = (value >>> 8)
    this._buffer.buffer[offset + 0] = (value & 0xFF)
  }
}

exports.Model = Model
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelBase()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base field model
 */
class FieldModelBase {
  /**
   * Initialize field model with the given buffer
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (buffer, offset) {
    this._buffer = buffer
    this._offset = offset
  }

  /**
   * Get the field offset
   * @this {!FieldModelBase}
   * @returns {!number} Field offset
   */
  get fbeOffset () {
    return this._offset
  }

  /**
   * Set the field offset
   * @this {!FieldModelBase}
   * @param {!number} offset Field offset
   */
  set fbeOffset (offset) {
    this._offset = offset
  }

  /**
   * Get the field size
   * @this {!FieldModelBase}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 0
  }

  /**
   * Get the field extra size
   * @this {!FieldModelBase}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    return 0
  }

  /**
   * Shift the current field offset
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   */
  fbeShift (offset) {
    this._offset += offset
  }

  /**
   * Unshift the current field offset
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   */
  fbeUnshift (offset) {
    this._offset -= offset
  }

  // Buffer I/O methods

  /**
   * Read boolean value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!boolean} Boolean value
   */
  readBool (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 1)
    return this._buffer.buffer[offset] !== 0
  }

  /**
   * Read byte value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Byte value
   */
  readByte (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 1)
    return this._buffer.buffer[offset]
  }

  /**
   * Read char value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!string} Char value
   */
  readChar (offset) {
    let code = this.readUInt8(offset)
    return String.fromCharCode(code)
  }

  /**
   * Read wide char value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!string} Wide char value
   */
  readWChar (offset) {
    let code = this.readUInt32(offset)
    return String.fromCharCode(code)
  }

  /**
   * Read Int8 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Int8 value
   */
  readInt8 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 1)
    if ((this._buffer.buffer[offset] & 0x80) === 0) {
      return (this._buffer.buffer[offset])
    }
    return ((0xFF - this._buffer.buffer[offset] + 1) * -1)
  }

  /**
   * Read UInt8 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} UInt8 value
   */
  readUInt8 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 1)
    return this._buffer.buffer[offset]
  }

  /**
   * Read Int16 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Int16 value
   */
  readInt16 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 2)
    let val = this._buffer.buffer[offset] | (this._buffer.buffer[offset + 1] << 8)
    return (val & 0x8000) ? val | 0xFFFF0000 : val
  }

  /**
   * Read UInt16 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} UInt16 value
   */
  readUInt16 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 2)
    return this._buffer.buffer[offset] | (this._buffer.buffer[offset + 1] << 8)
  }

  /**
   * Read Int32 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Int32 value
   */
  readInt32 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 4)
    return (
      (this._buffer.buffer[offset + 0] << 0) |
      (this._buffer.buffer[offset + 1] << 8) |
      (this._buffer.buffer[offset + 2] << 16) |
      (this._buffer.buffer[offset + 3] << 24))
  }

  /**
   * Read UInt32 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} UInt32 value
   */
  readUInt32 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 4)
    return (
      (this._buffer.buffer[offset + 0] << 0) |
      (this._buffer.buffer[offset + 1] << 8) |
      (this._buffer.buffer[offset + 2] << 16)) +
      (this._buffer.buffer[offset + 3] * 0x1000000)
  }

  /**
   * Read Int64 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!Int64} Int64 value
   */
  readInt64 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 8)
    let low = (
      (this._buffer.buffer[offset + 0] << 0) |
      (this._buffer.buffer[offset + 1] << 8) |
      (this._buffer.buffer[offset + 2] << 16) |
      (this._buffer.buffer[offset + 3] << 24))
    let high = (
      (this._buffer.buffer[offset + 4] << 0) |
      (this._buffer.buffer[offset + 5] << 8) |
      (this._buffer.buffer[offset + 6] << 16) |
      (this._buffer.buffer[offset + 7] << 24))
    return new Int64(low, high)
  }

  /**
   * Read UInt64 value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!UInt64} UInt64 value
   */
  readUInt64 (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 8)
    let low = (
      (this._buffer.buffer[offset + 0] << 0) |
      (this._buffer.buffer[offset + 1] << 8) |
      (this._buffer.buffer[offset + 2] << 16) |
      (this._buffer.buffer[offset + 3] << 24))
    let high = (
      (this._buffer.buffer[offset + 4] << 0) |
      (this._buffer.buffer[offset + 5] << 8) |
      (this._buffer.buffer[offset + 6] << 16) +
      (this._buffer.buffer[offset + 7] << 24))
    return new UInt64(low, high)
  }

  /**
   * Read float value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Float value
   */
  readFloat (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 4)
    return ieee754read(this._buffer.buffer, offset, true, 23, 4)
  }

  /**
   * Read double value from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @returns {!number} Double value
   */
  readDouble (offset) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 4)
    return ieee754read(this._buffer.buffer, offset, true, 52, 8)
  }

  /**
   * Read bytes from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @returns {!Uint8Array} Bytes buffer
   */
  readBytes (offset, size) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, size)
    return this._buffer.buffer.slice(offset, offset + size)
  }

  /**
   * Read string from the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @returns {!string} String value
   */
  readString (offset, size) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, size)
    return utf8decode(this._buffer.buffer, offset, size)
  }

  /**
   * Write boolean value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!boolean} value Boolean value
   */
  writeBool (offset, value) {
    let byte = value ? 1 : 0
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 1, byte, 0, 0xFF)
    this._buffer.buffer[offset] = (byte & 0xFF)
  }

  /**
   * Write byte value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Byte value
   */
  writeByte (offset, value) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 1, value, 0, 0xFF)
    this._buffer.buffer[offset] = (value & 0xFF)
  }

  /**
   * Write char value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!string} value Char value
   */
  writeChar (offset, value) {
    let code = value.charCodeAt(0)
    this.writeUInt8(offset, code)
  }

  /**
   * Write wide char value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!string} value Wide char value
   */
  writeWChar (offset, value) {
    let code = value.charCodeAt(0)
    this.writeUInt32(offset, code)
  }

  /**
   * Write Int8 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Int8 value
   */
  writeInt8 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 1, value, -0x80, 0x7F)
    if (value < 0) {
      value = 0xFF + value + 1
    }
    this._buffer.buffer[offset] = (value & 0xFF)
  }

  /**
   * Write UInt8 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value UInt8 value
   */
  writeUInt8 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 1, value, 0, 0xFF)
    this._buffer.buffer[offset] = (value & 0xFF)
  }

  /**
   * Write Int16 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Int16 value
   */
  writeInt16 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 2, value, -0x8000, 0x7FFF)
    this._buffer.buffer[offset + 0] = (value & 0xFF)
    this._buffer.buffer[offset + 1] = (value >>> 8)
  }

  /**
   * Write UInt16 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value UInt16 value
   */
  writeUInt16 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 2, value, 0, 0xFFFF)
    this._buffer.buffer[offset + 0] = (value & 0xFF)
    this._buffer.buffer[offset + 1] = (value >>> 8)
  }

  /**
   * Write Int32 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Int32 value
   */
  writeInt32 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 4, value, -0x80000000, 0x7FFFFFFF)
    this._buffer.buffer[offset + 0] = (value & 0xFF)
    this._buffer.buffer[offset + 1] = (value >>> 8)
    this._buffer.buffer[offset + 2] = (value >>> 16)
    this._buffer.buffer[offset + 3] = (value >>> 24)
  }

  /**
   * Write UInt32 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value UInt32 value
   */
  writeUInt32 (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 4, value, 0, 0xFFFFFFFF)
    this._buffer.buffer[offset + 0] = (value & 0xFF)
    this._buffer.buffer[offset + 1] = (value >>> 8)
    this._buffer.buffer[offset + 2] = (value >>> 16)
    this._buffer.buffer[offset + 3] = (value >>> 24)
  }

  /**
   * Write Int64 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!Int64} value Int64 value
   */
  writeInt64 (offset, value) {
    value = Int64.fromValue(value)
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 8)
    this._buffer.buffer[offset + 0] = (value.low & 0xFF)
    this._buffer.buffer[offset + 1] = (value.low >>> 8)
    this._buffer.buffer[offset + 2] = (value.low >>> 16)
    this._buffer.buffer[offset + 3] = (value.low >>> 24)
    this._buffer.buffer[offset + 4] = (value.high & 0xFF)
    this._buffer.buffer[offset + 5] = (value.high >>> 8)
    this._buffer.buffer[offset + 6] = (value.high >>> 16)
    this._buffer.buffer[offset + 7] = (value.high >>> 24)
  }

  /**
   * Write UInt64 value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!UInt64} value UInt64 value
   */
  writeUInt64 (offset, value) {
    value = UInt64.fromValue(value)
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, 8)
    this._buffer.buffer[offset + 0] = (value.low & 0xFF)
    this._buffer.buffer[offset + 1] = (value.low >>> 8)
    this._buffer.buffer[offset + 2] = (value.low >>> 16)
    this._buffer.buffer[offset + 3] = (value.low >>> 24)
    this._buffer.buffer[offset + 4] = (value.high & 0xFF)
    this._buffer.buffer[offset + 5] = (value.high >>> 8)
    this._buffer.buffer[offset + 6] = (value.high >>> 16)
    this._buffer.buffer[offset + 7] = (value.high >>> 24)
  }

  /**
   * Write float value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Float value
   */
  writeFloat (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 4, value, -3.4028234663852886e+38, 3.4028234663852886e+38)
    ieee754write(this._buffer.buffer, offset, value, true, 23, 4)
  }

  /**
   * Write double value into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Double value
   */
  writeDouble (offset, value) {
    value = +value
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkValue(offset, 8, value, -1.7976931348623157E+308, 1.7976931348623157E+308)
    ieee754write(this._buffer.buffer, offset, value, true, 52, 8)
  }

  /**
   * Write bytes into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!Uint8Array} value Bytes buffer value
   * @param {number=} valueOffset Bytes buffer offset, defaults is 0
   * @param {number=} valueSize Bytes buffer size, defaults is value.length
   */
  writeBytes (offset, value, valueOffset = 0, valueSize = undefined) {
    if (valueSize == null) {
      valueSize = value.length
    }

    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, valueSize)
    for (let i = 0; i < valueSize; i++) {
      this._buffer.buffer[offset + i] = value[valueOffset + i]
    }
  }

  /**
   * Write byte value of the given count into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!number} value Byte value
   * @param {!number} valueCount Count
   */
  writeCount (offset, value, valueCount) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, valueCount)
    for (let i = 0; i < valueCount; i++) {
      this._buffer.buffer[offset + i] = (value & 0xFF)
    }
  }

  /**
   * Write string into the field model buffer
   * @this {!FieldModelBase}
   * @param {!number} offset Offset
   * @param {!string} value String value
   * @param {number=} size String size
   */
  writeString (offset, value, size) {
    offset = offset >>> 0
    offset += this._buffer.offset
    this._buffer.checkOffset(offset, size)
    utf8encode(this._buffer.buffer, offset, value)
  }
}

exports.FieldModelBase = FieldModelBase
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModel()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding field model
 */
class FieldModel extends FieldModelBase {
  /**
   * Check if the value is valid
   * @this {!FieldModel}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    return true
  }
}

exports.FieldModel = FieldModel
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModel(const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _TYPE_ field model
 */
class FieldModel_NAME_ extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModel_NAME_}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return _SIZE_
  }

  /**
   * Get the value
   * @this {!FieldModel_NAME_}
   * @param {_BASE_=} defaults Default value, defaults is _DEFAULTS_
   * @returns {!_BASE_} Result value
   */
  get (defaults = _DEFAULTS_) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    return this.read_NAME_(this.fbeOffset)
  }

  /**
   * Set the value
   * @this {!FieldModel_NAME_}
   * @param {!_BASE_} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    this.write_NAME_(this.fbeOffset, value)
  }
}

exports.FieldModel_NAME_ = FieldModel_NAME_
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelDecimal()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding decimal field model
 */
class FieldModelDecimal extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModelDecimal}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 16
  }

  /**
   * Get the decimal value
   * @this {!FieldModelDecimal}
   * @param {Big=} defaults Default value, defaults is new Big(0)
   * @returns {!Big} Result value
   */
  get (defaults = new Big(0)) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    // Read decimal parts
    let low = this.readUInt32(this.fbeOffset)
    let mid = this.readUInt32(this.fbeOffset + 4)
    let high = this.readUInt32(this.fbeOffset + 8)
    let flags = this.readUInt32(this.fbeOffset + 12)

    // Calculate decimal value
    let negative = (flags & 0x80000000) !== 0
    let scale = (flags & 0x7FFFFFFF) >> 16
    let result = new Big(0)
    result = result.add(new Big(high).mul('18446744073709551616'))
    result = result.add(new Big(mid).mul('4294967296'))
    result = result.add(new Big(low))
    result = result.div(Math.pow(10, scale))
    if (negative) {
      result.s = -1
    }

    return result
  }

  /**
   * Set the decimal value
   * @this {!FieldModelDecimal}
   * @param {!Big} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    // Extract decimal parts
    let negative = value.s < 0
    let scale = Math.max(0, value.c.length - 1 - value.e)
    let number = value.mul(Math.pow(10, scale)).abs()

    // Check for decimal scale overflow
    if ((scale < 0) || (scale > 28)) {
      // Value scale exceeds .NET Decimal limit of [0, 28]
      this.writeCount(this.fbeOffset, 0, this.fbeSize)
      return
    }

    // Write unscaled value to bytes 0-11
    let index = 0
    while (number > 0) {
      // Check for decimal number overflow
      if (index > 11) {
        // Value too big for .NET Decimal (bit length is limited to [0, 96])
        this.writeCount(this.fbeOffset, 0, this.fbeSize)
        return
      }
      let byte = parseInt(number.mod(256))
      this.writeByte(this.fbeOffset + index, byte)
      number = number.div(256).round(0, 0)
      index++
    }

    // Fill remaining bytes with zeros
    while (index < 12) {
      this.writeByte(this.fbeOffset + index, 0)
      index++
    }

    // Write scale at byte 14
    this.writeByte(this.fbeOffset + 14, scale)

    // Write signum at byte 15
    this.writeByte(this.fbeOffset + 15, (negative ? 0x80 : 0))
  }
}

exports.FieldModelDecimal = FieldModelDecimal
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelTimestamp()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding timestamp field model
 */
class FieldModelTimestamp extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModelTimestamp}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 8
  }

  /**
   * Get the timestamp value
   * @this {!FieldModelTimestamp}
   * @param {Date=} defaults Default value, defaults is new Date(0)
   * @returns {!Date} Result value
   */
  get (defaults = new Date(0)) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    let nanoseconds = this.readUInt64(this.fbeOffset)
    return new Date(Math.round(nanoseconds / 1000000))
  }

  /**
   * Set the timestamp value
   * @this {!FieldModelTimestamp}
   * @param {!Date} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let nanoseconds = UInt64.fromNumber(value.getTime()).mul(1000000)
    this.writeUInt64(this.fbeOffset, nanoseconds)
  }
}

exports.FieldModelTimestamp = FieldModelTimestamp
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelUUID()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding UUID field model
 */
class FieldModelUUID extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModelUUID}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 16
  }

  /**
   * Get the UUID value
   * @this {!FieldModelUUID}
   * @param {UUID=} defaults Default value, defaults is UUID.nil()
   * @returns {!UUID} Result value
   */
  get (defaults = UUID.nil()) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    return new UUID(this.readBytes(this.fbeOffset, 16))
  }

  /**
   * Set the UUID value
   * @this {!FieldModelUUID}
   * @param {!UUID} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    this.writeBytes(this.fbeOffset, value.data)
  }
}

exports.FieldModelUUID = FieldModelUUID
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelBytes()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding bytes field model
 */
class FieldModelBytes extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModelBytes}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelBytes}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeBytesOffset = this.readUInt32(this.fbeOffset)
    if ((fbeBytesOffset === 0) || ((this._buffer.offset + fbeBytesOffset + 4) > this._buffer.size)) {
      return 0
    }

    let fbeBytesSize = this.readUInt32(fbeBytesOffset)
    return 4 + fbeBytesSize
  }

  /**
   * Check if the bytes value is valid
   * @this {!FieldModelBytes}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeBytesOffset = this.readUInt32(this.fbeOffset)
    if (fbeBytesOffset === 0) {
      return true
    }

    if ((this._buffer.offset + fbeBytesOffset + 4) > this._buffer.size) {
      return false
    }

    let fbeBytesSize = this.readUInt32(fbeBytesOffset)
    return (this._buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) <= this._buffer.size
  }

  /**
   * Get the bytes value
   * @this {!FieldModelBytes}
   * @param {Uint8Array=} defaults Default value, defaults is Uint8Array(0)
   * @returns {!Uint8Array} Result value
   */
  get (defaults = new Uint8Array(0)) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    let fbeBytesOffset = this.readUInt32(this.fbeOffset)
    if (fbeBytesOffset === 0) {
      return defaults
    }

    console.assert(((this._buffer.offset + fbeBytesOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + fbeBytesOffset + 4) > this._buffer.size) {
      return defaults
    }

    let fbeBytesSize = this.readUInt32(fbeBytesOffset)
    console.assert(((this._buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) > this._buffer.size) {
      return defaults
    }

    return this.readBytes(fbeBytesOffset + 4, fbeBytesSize)
  }

  /**
   * Set the bytes value
   * @this {!FieldModelBytes}
   * @param {!Uint8Array} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let fbeBytesSize = value.length
    let fbeBytesOffset = this._buffer.allocate(4 + fbeBytesSize) - this._buffer.offset
    console.assert(((fbeBytesOffset > 0) && ((this._buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) <= this._buffer.size)), 'Model is broken!')
    if (((fbeBytesOffset <= 0) || ((this._buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) > this._buffer.size))) {
      return
    }

    this.writeUInt32(this.fbeOffset, fbeBytesOffset)
    this.writeUInt32(fbeBytesOffset, fbeBytesSize)
    this.writeBytes(fbeBytesOffset + 4, value)
  }
}

exports.FieldModelBytes = FieldModelBytes
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelString()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding string field model
 */
class FieldModelString extends FieldModel {
  /**
   * Get the field size
   * @this {!FieldModelString}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelString}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeStringOffset = this.readUInt32(this.fbeOffset)
    if ((fbeStringOffset === 0) || ((this._buffer.offset + fbeStringOffset + 4) > this._buffer.size)) {
      return 0
    }

    let fbeStringSize = this.readUInt32(fbeStringOffset)
    return 4 + fbeStringSize
  }

  /**
   * Check if the string value is valid
   * @this {!FieldModelString}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeStringOffset = this.readUInt32(this.fbeOffset)
    if (fbeStringOffset === 0) {
      return true
    }

    if ((this._buffer.offset + fbeStringOffset + 4) > this._buffer.size) {
      return false
    }

    let fbeStringSize = this.readUInt32(fbeStringOffset)
    return (this._buffer.offset + fbeStringOffset + 4 + fbeStringSize) <= this._buffer.size
  }

  /**
   * Get the string value
   * @this {!FieldModelString}
   * @param {string=} defaults Default value, defaults is ''
   * @returns {!string} Result value
   */
  get (defaults = '') {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    let fbeStringOffset = this.readUInt32(this.fbeOffset)
    if (fbeStringOffset === 0) {
      return defaults
    }

    console.assert(((this._buffer.offset + fbeStringOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + fbeStringOffset + 4) > this._buffer.size) {
      return defaults
    }

    let fbeStringSize = this.readUInt32(fbeStringOffset)
    console.assert(((this._buffer.offset + fbeStringOffset + 4 + fbeStringSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + fbeStringOffset + 4 + fbeStringSize) > this._buffer.size) {
      return defaults
    }

    return this.readString(fbeStringOffset + 4, fbeStringSize)
  }

  /**
   * Set the string value
   * @this {!FieldModelString}
   * @param {!string} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let fbeStringSize = utf8count(value)
    let fbeStringOffset = this._buffer.allocate(4 + fbeStringSize) - this._buffer.offset
    console.assert(((fbeStringOffset > 0) && ((this._buffer.offset + fbeStringOffset + 4 + fbeStringSize) <= this._buffer.size)), 'Model is broken!')
    if ((fbeStringOffset <= 0) || ((this._buffer.offset + fbeStringOffset + 4 + fbeStringSize) > this._buffer.size)) {
      return
    }

    this.writeUInt32(this.fbeOffset, fbeStringOffset)
    this.writeUInt32(fbeStringOffset, fbeStringSize)
    this.writeString(fbeStringOffset + 4, value, fbeStringSize)
  }
}

exports.FieldModelString = FieldModelString
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelOptional()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding optional field model
 */
class FieldModelOptional extends FieldModel {
  /**
   * Initialize optional field model with the given value field model and buffer
   * @param {!FieldModel} model Value field model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
    this._model.fbeOffset = 0
  }

  /**
   * Get the field size
   * @this {!FieldModelOptional}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 1 + 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelOptional}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if (!this.hasValue) {
      return 0
    }

    let fbeOptionalOffset = this.readUInt32(this.fbeOffset + 1)
    if ((fbeOptionalOffset === 0) || ((this._buffer.offset + fbeOptionalOffset + 4) > this._buffer.size)) {
      return 0
    }

    this._buffer.shift(fbeOptionalOffset)
    let fbeResult = this.value.fbeSize + this.value.fbeExtra
    this._buffer.unshift(fbeOptionalOffset)
    return fbeResult
  }

  /**
   * Checks if the object contains a value
   * @this {!FieldModelOptional}
   * @returns {!boolean} Optional has value flag
   */
  get hasValue () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return false
    }

    let fbeHasValue = this.readUInt8(this.fbeOffset)
    return fbeHasValue !== 0
  }

  /**
   * Get the base field model value
   * @this {!FieldModelOptional}
   * @returns {!FieldModel} Base field model value
   */
  get value () {
    return this._model
  }

  /**
   * Check if the optional value is valid
   * @this {!FieldModelOptional}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeHasValue = this.readUInt8(this.fbeOffset)
    if (fbeHasValue === 0) {
      return true
    }

    let fbeOptionalOffset = this.readUInt32(this.fbeOffset + 1)
    if (fbeOptionalOffset === 0) {
      return false
    }

    this._buffer.shift(fbeOptionalOffset)
    let fbeResult = this.value.verify()
    this._buffer.unshift(fbeOptionalOffset)
    return fbeResult
  }

  /**
   * Get the optional value (being phase)
   * @this {!FieldModelOptional}
   * @returns {!number} Optional begin offset
   */
  getBegin () {
    if (!this.hasValue) {
      return 0
    }

    let fbeOptionalOffset = this.readUInt32(this.fbeOffset + 1)
    console.assert((fbeOptionalOffset > 0), 'Model is broken!')
    if (fbeOptionalOffset <= 0) {
      return 0
    }

    this._buffer.shift(fbeOptionalOffset)
    return fbeOptionalOffset
  }

  /**
   * Get the optional value (end phase)
   * @this {!FieldModelOptional}
   * @param {!number} begin Optional begin offset
   */
  getEnd (begin) {
    this._buffer.unshift(begin)
  }

  /**
   * Get the optional value
   * @this {!FieldModelOptional}
   * @param {object=} defaults Default value, defaults is undefined
   * @returns {object} Result value
   */
  get (defaults = undefined) {
    let fbeBegin = this.getBegin()
    if (fbeBegin === 0) {
      return defaults
    }
    let optional = this.value.get()
    this.getEnd(fbeBegin)
    return optional
  }

  /**
   * Set the optional value (begin phase)
   * @this {!FieldModelOptional}
   * @param {boolean} hasValue Optional has value flag
   * @returns {!number} Optional begin offset
   */
  setBegin (hasValue) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    this.writeUInt8(this.fbeOffset, (hasValue ? 1 : 0))
    if (!hasValue) {
      return 0
    }

    let fbeOptionalSize = this.value.fbeSize
    let fbeOptionalOffset = this._buffer.allocate(fbeOptionalSize) - this._buffer.offset
    console.assert(((fbeOptionalOffset > 0) && ((this._buffer.offset + fbeOptionalOffset + fbeOptionalSize) <= this._buffer.size)), 'Model is broken!')
    if ((fbeOptionalOffset <= 0) || ((this._buffer.offset + fbeOptionalOffset + fbeOptionalSize) > this._buffer.size)) {
      return 0
    }

    this.writeUInt32(this.fbeOffset + 1, fbeOptionalOffset)

    this._buffer.shift(fbeOptionalOffset)
    return fbeOptionalOffset
  }

  /**
   * Set the optional value (end phase)
   * @this {!FieldModelOptional}
   * @param {!number} begin Optional begin offset
   */
  setEnd (begin) {
    this._buffer.unshift(begin)
  }

  /**
   * Set the optional value
   * @this {!FieldModelOptional}
   * @param {object} optional Optional value
   */
  set (optional) {
    let fbeBegin = this.setBegin(optional != null)
    if (fbeBegin === 0) {
      return
    }
    this.value.set(optional)
    this.setEnd(fbeBegin)
  }
}

exports.FieldModelOptional = FieldModelOptional
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelArray()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding array field model
 */
class FieldModelArray extends FieldModel {
  /**
   * Initialize array field model with the given value field model, buffer and array size
   * @param {!FieldModel} model Value field model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @constructor
   */
  constructor (model, buffer, offset, size) {
    super(buffer, offset)
    this._model = model
    this._size = size
  }

  /**
   * Get the field size
   * @this {!FieldModelArray}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return this._size * this._model.fbeSize
  }

  /**
   * Get the field extra size
   * @this {!FieldModelArray}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    return 0
  }

  /**
   * Get the array offset
   * @this {!FieldModelArray}
   * @returns {!number} Array offset
   */
  get offset () {
    return 0
  }

  /**
   * Get the array size
   * @this {!FieldModelArray}
   * @returns {!number} Array size
   */
  get size () {
    return this._size
  }

  /**
   * Array index operator
   * @this {!FieldModelArray}
   * @param {!number} index Array index
   * @returns {!FieldModel} Base field model value
   */
  getItem (index) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if (index >= this._size) {
      throw new Error('Index is out of bounds!')
    }

    this._model.fbeOffset = this.fbeOffset
    this._model.fbeShift(index * this._model.fbeSize)
    return this._model
  }

  /**
   * Check if the array is valid
   * @this {!FieldModelArray}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return false
    }

    this._model.fbeOffset = this.fbeOffset
    for (let i = 0; i < this._size; i++) {
      if (!this._model.verify()) {
        return false
      }
      this._model.fbeShift(this._model.fbeSize)
    }

    return true
  }

  /**
   * Get the array
   * @this {!FieldModelArray}
   * @param {Array=} values Array values, defaults is []
   * @returns {!Array} Result array
   */
  get (values = []) {
    values.length = 0

    let fbeModel = this.getItem(0)
    for (let i = 0; i < this._size; i++) {
      let value = fbeModel.get()
      values.push(value)
      fbeModel.fbeShift(fbeModel.fbeSize)
    }

    return values
  }

  /**
   * Set the array
   * @this {!FieldModelArray}
   * @param {!Array} values Array values
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let fbeModel = this.getItem(0)
    for (let i = 0; i < Math.min(values.length, this._size); i++) {
      fbeModel.set(values[i])
      fbeModel.fbeShift(fbeModel.fbeSize)
    }
  }
}

exports.FieldModelArray = FieldModelArray
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelVector()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding vector field model
 */
class FieldModelVector extends FieldModel {
  /**
   * Initialize vector field model with the given value field model and buffer
   * @param {!FieldModel} model Value field model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
  }

  /**
   * Get the field size
   * @this {!FieldModelVector}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelVector}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeVectorOffset = this.readUInt32(this.fbeOffset)
    if ((fbeVectorOffset === 0) || ((this._buffer.offset + fbeVectorOffset + 4) > this._buffer.size)) {
      return 0
    }

    let fbeVectorSize = this.readUInt32(fbeVectorOffset)

    let fbeResult = 4
    this._model.fbeOffset = fbeVectorOffset + 4
    for (let i = 0; i < fbeVectorSize; i++) {
      fbeResult += this._model.fbeSize + this._model.fbeExtra
      this._model.fbeShift(this._model.fbeSize)
    }
    return fbeResult
  }

  /**
   * Get the vector offset
   * @this {!FieldModelVector}
   * @returns {!number} Vector offset
   */
  get offset () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeVectorOffset = this.readUInt32(this.fbeOffset)
    return fbeVectorOffset
  }

  /**
   * Get the vector size
   * @this {!FieldModelVector}
   * @returns {!number} Vector size
   */
  get size () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeVectorOffset = this.readUInt32(this.fbeOffset)
    if ((fbeVectorOffset === 0) || ((this._buffer.offset + fbeVectorOffset + 4) > this._buffer.size)) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeVectorSize = this.readUInt32(fbeVectorOffset)
    return fbeVectorSize
  }

  /**
   * Vector index operator
   * @this {!FieldModelVector}
   * @param {!number} index Vector index
   * @returns {!FieldModel} Base field model value
   */
  getItem (index) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')

    let fbeVectorOffset = this.readUInt32(this.fbeOffset)
    console.assert(((fbeVectorOffset > 0) && ((this._buffer.offset + fbeVectorOffset + 4) <= this._buffer.size)), 'Model is broken!')

    let fbeVectorSize = this.readUInt32(fbeVectorOffset)
    if (index >= fbeVectorSize) {
      throw new Error('Index is out of bounds!')
    }

    this._model.fbeOffset = fbeVectorOffset + 4
    this._model.fbeShift(index * this._model.fbeSize)
    return this._model
  }

  /**
   * Resize the vector and get its first model
   * @this {!FieldModelVector}
   * @param {!number} size Size
   * @returns {!FieldModel} Base field model value
   */
  resize (size) {
    let fbeVectorSize = size * this._model.fbeSize
    let fbeVectorOffset = this._buffer.allocate(4 + fbeVectorSize) - this._buffer.offset
    console.assert(((fbeVectorOffset > 0) && ((this._buffer.offset + fbeVectorOffset + 4) <= this._buffer.size)), 'Model is broken!')

    this.writeUInt32(this.fbeOffset, fbeVectorOffset)
    this.writeUInt32(fbeVectorOffset, size)
    this.writeCount(fbeVectorOffset + 4, 0, fbeVectorSize)

    this._model.fbeOffset = fbeVectorOffset + 4
    return this._model
  }

  /**
   * Check if the vector is valid
   * @this {!FieldModelVector}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeVectorOffset = this.readUInt32(this.fbeOffset)
    if (fbeVectorOffset === 0) {
      return true
    }

    if ((this._buffer.offset + fbeVectorOffset + 4) > this._buffer.size) {
      return false
    }

    let fbeVectorSize = this.readUInt32(fbeVectorOffset)

    this._model.fbeOffset = fbeVectorOffset + 4
    for (let i = 0; i < fbeVectorSize; i++) {
      if (!this._model.verify()) {
        return false
      }
      this._model.fbeShift(this._model.fbeSize)
    }

    return true
  }

  /**
   * Get the vector
   * @this {!FieldModelVector}
   * @param {Array=} values Vector values, defaults is []
   * @returns {!Array} Result vector values
   */
  get (values = []) {
    values.length = 0

    let fbeVectorSize = this.size
    if (fbeVectorSize === 0) {
      return values
    }

    let fbeModel = this.getItem(0)
    for (let i = 0; i < fbeVectorSize; i++) {
      let value = fbeModel.get()
      values.push(value)
      fbeModel.fbeShift(fbeModel.fbeSize)
    }

    return values
  }

  /**
   * Set the vector
   * @this {!FieldModelVector}
   * @param {!Array} values Vector values
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let fbeModel = this.resize(values.length)
    for (let value of values) {
      fbeModel.set(value)
      fbeModel.fbeShift(fbeModel.fbeSize)
    }
  }
}

exports.FieldModelVector = FieldModelVector
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelSet()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding set field model
 */
class FieldModelSet extends FieldModel {
  /**
   * Initialize set field model with the given value field model and buffer
   * @param {!FieldModel} model Value field model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
  }

  /**
   * Get the field size
   * @this {!FieldModelSet}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelSet}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeSetOffset = this.readUInt32(this.fbeOffset)
    if ((fbeSetOffset === 0) || ((this._buffer.offset + fbeSetOffset + 4) > this._buffer.size)) {
      return 0
    }

    let fbeSetSize = this.readUInt32(fbeSetOffset)

    let fbeResult = 4
    this._model.fbeOffset = fbeSetOffset + 4
    for (let i = 0; i < fbeSetSize; i++) {
      fbeResult += this._model.fbeSize + this._model.fbeExtra
      this._model.fbeShift(this._model.fbeSize)
    }
    return fbeResult
  }

  /**
   * Get the set offset
   * @this {!FieldModelSet}
   * @returns {!number} Set offset
   */
  get offset () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeSetOffset = this.readUInt32(this.fbeOffset)
    return fbeSetOffset
  }

  /**
   * Get the set size
   * @this {!FieldModelSet}
   * @returns {!number} Set size
   */
  get size () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeSetOffset = this.readUInt32(this.fbeOffset)
    if ((fbeSetOffset === 0) || ((this._buffer.offset + fbeSetOffset + 4) > this._buffer.size)) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeSetSize = this.readUInt32(fbeSetOffset)
    return fbeSetSize
  }

  /**
   * Set index operator
   * @this {!FieldModelSet}
   * @param {!number} index Set index
   * @returns {!FieldModel} Base field model value
   */
  getItem (index) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')

    let fbeSetOffset = this.readUInt32(this.fbeOffset)
    console.assert(((fbeSetOffset > 0) && ((this._buffer.offset + fbeSetOffset + 4) <= this._buffer.size)), 'Model is broken!')

    let fbeSetSize = this.readUInt32(fbeSetOffset)
    if (index >= fbeSetSize) {
      throw new Error('Index is out of bounds!')
    }

    this._model.fbeOffset = fbeSetOffset + 4
    this._model.fbeShift(index * this._model.fbeSize)
    return this._model
  }

  /**
   * Resize the set and get its first model
   * @this {!FieldModelSet}
   * @param {!number} size Size
   * @returns {!FieldModel} Base field model value
   */
  resize (size) {
    let fbeSetSize = size * this._model.fbeSize
    let fbeSetOffset = this._buffer.allocate(4 + fbeSetSize) - this._buffer.offset
    console.assert(((fbeSetOffset > 0) && ((this._buffer.offset + fbeSetOffset + 4) <= this._buffer.size)), 'Model is broken!')

    this.writeUInt32(this.fbeOffset, fbeSetOffset)
    this.writeUInt32(fbeSetOffset, size)
    this.writeCount(fbeSetOffset + 4, 0, fbeSetSize)

    this._model.fbeOffset = fbeSetOffset + 4
    return this._model
  }

  /**
   * Check if the set value is valid
   * @this {!FieldModelSet}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeSetOffset = this.readUInt32(this.fbeOffset)
    if (fbeSetOffset === 0) {
      return true
    }

    if ((this._buffer.offset + fbeSetOffset + 4) > this._buffer.size) {
      return false
    }

    let fbeSetSize = this.readUInt32(fbeSetOffset)

    this._model.fbeOffset = fbeSetOffset + 4
    for (let i = 0; i < fbeSetSize; i++) {
      if (!this._model.verify()) {
        return false
      }
      this._model.fbeShift(this._model.fbeSize)
    }

    return true
  }

  /**
   * Get the set value
   * @this {!FieldModelSet}
   * @param {Set=} values Set values, defaults is new Set()
   * @returns {!Set} Result set values
   */
  get (values = new Set()) {
    values.clear()

    let fbeSetSize = this.size
    if (fbeSetSize === 0) {
      return values
    }

    let fbeModel = this.getItem(0)
    for (let i = 0; i < fbeSetSize; i++) {
      let value = fbeModel.get()
      values.add(value)
      fbeModel.fbeShift(fbeModel.fbeSize)
    }

    return values
  }

  /**
   * Set the set value
   * @this {!FieldModelSet}
   * @param {!Set} values Set values
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let fbeModel = this.resize(values.size)
    for (let value of values) {
      fbeModel.set(value)
      fbeModel.fbeShift(fbeModel.fbeSize)
    }
  }
}

exports.FieldModelSet = FieldModelSet
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFieldModelMap()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding map field model
 */
class FieldModelMap extends FieldModel {
  /**
   * Initialize map field model with the given key/value field models and buffer
   * @param {!FieldModel} modelKey Key field model
   * @param {!FieldModel} modelValue Value field model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (modelKey, modelValue, buffer, offset) {
    super(buffer, offset)
    this._modelKey = modelKey
    this._modelValue = modelValue
  }

  /**
   * Get the field size
   * @this {!FieldModelMap}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return 4
  }

  /**
   * Get the field extra size
   * @this {!FieldModelMap}
   * @returns {!number} Field extra size
   */
  get fbeExtra () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeMapOffset = this.readUInt32(this.fbeOffset)
    if ((fbeMapOffset === 0) || ((this._buffer.offset + fbeMapOffset + 4) > this._buffer.size)) {
      return 0
    }

    let fbeMapSize = this.readUInt32(fbeMapOffset)

    let fbeResult = 4
    this._modelKey.fbeOffset = fbeMapOffset + 4
    this._modelValue.fbeOffset = fbeMapOffset + 4 + this._modelKey.fbeSize
    for (let i = 0; i < fbeMapSize; i++) {
      fbeResult += this._modelKey.fbeSize + this._modelKey.fbeExtra
      this._modelKey.fbeShift(this._modelKey.fbeSize + this._modelValue.fbeSize)
      fbeResult += this._modelValue.fbeSize + this._modelValue.fbeExtra
      this._modelValue.fbeShift(this._modelKey.fbeSize + this._modelValue.fbeSize)
    }
    return fbeResult
  }

  /**
   * Get the map offset
   * @this {!FieldModelMap}
   * @returns {!number} Map offset
   */
  get offset () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeMapOffset = this.readUInt32(this.fbeOffset)
    return fbeMapOffset
  }

  /**
   * Get the map size
   * @this {!FieldModelMap}
   * @returns {!number} Map size
   */
  get size () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let fbeMapOffset = this.readUInt32(this.fbeOffset)
    if ((fbeMapOffset === 0) || ((this._buffer.offset + fbeMapOffset + 4) > this._buffer.size)) {
      return 0
    }

    // noinspection UnnecessaryLocalVariableJS
    let fbeMapSize = this.readUInt32(fbeMapOffset)
    return fbeMapSize
  }

  /**
   * Map index operator
   * @this {!FieldModelMap}
   * @param {!number} index Map index
   * @returns {[!FieldModel]} Base field model value
   */
  getItem (index) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')

    let fbeMapOffset = this.readUInt32(this.fbeOffset)
    console.assert(((fbeMapOffset > 0) && ((this._buffer.offset + fbeMapOffset + 4) <= this._buffer.size)), 'Model is broken!')

    let fbeMapSize = this.readUInt32(fbeMapOffset)
    if (index >= fbeMapSize) {
      throw new Error('Index is out of bounds!')
    }

    this._modelKey.fbeOffset = fbeMapOffset + 4
    this._modelValue.fbeOffset = fbeMapOffset + 4 + this._modelKey.fbeSize
    this._modelKey.fbeShift(index * (this._modelKey.fbeSize + this._modelValue.fbeSize))
    this._modelValue.fbeShift(index * (this._modelKey.fbeSize + this._modelValue.fbeSize))
    return [this._modelKey, this._modelValue]
  }

  /**
   * Resize the map and get its first model
   * @this {!FieldModelMap}
   * @param {!number} size Size
   * @returns {[!FieldModel]} Base field model value
   */
  resize (size) {
    let fbeMapSize = size * (this._modelKey.fbeSize + this._modelValue.fbeSize)
    let fbeMapOffset = this._buffer.allocate(4 + fbeMapSize) - this._buffer.offset
    console.assert(((fbeMapOffset > 0) && ((this._buffer.offset + fbeMapOffset + 4) <= this._buffer.size)), 'Model is broken!')

    this.writeUInt32(this.fbeOffset, fbeMapOffset)
    this.writeUInt32(fbeMapOffset, size)
    this.writeCount(fbeMapOffset + 4, 0, fbeMapSize)

    this._modelKey.fbeOffset = fbeMapOffset + 4
    this._modelValue.fbeOffset = fbeMapOffset + 4 + this._modelKey.fbeSize
    return [this._modelKey, this._modelValue]
  }

  /**
   * Check if the map is valid
   * @this {!FieldModelMap}
   * @returns {!boolean} Field model valid state
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return true
    }

    let fbeMapOffset = this.readUInt32(this.fbeOffset)
    if (fbeMapOffset === 0) {
      return true
    }

    if ((this._buffer.offset + fbeMapOffset + 4) > this._buffer.size) {
      return false
    }

    let fbeMapSize = this.readUInt32(fbeMapOffset)

    this._modelKey.fbeOffset = fbeMapOffset + 4
    this._modelValue.fbeOffset = fbeMapOffset + 4 + this._modelKey.fbeSize
    for (let i = 0; i < fbeMapSize; i++) {
      if (!this._modelKey.verify()) {
        return false
      }
      this._modelKey.fbeShift(this._modelKey.fbeSize + this._modelValue.fbeSize)
      if (!this._modelValue.verify()) {
        return false
      }
      this._modelValue.fbeShift(this._modelKey.fbeSize + this._modelValue.fbeSize)
    }

    return true
  }

  /**
   * Get the map
   * @this {!FieldModelMap}
   * @param {Map=} values Map values, defaults is new Map()
   * @returns {!Map} Result map values
   */
  get (values = new Map()) {
    values.clear()

    let fbeMapSize = this.size
    if (fbeMapSize === 0) {
      return values
    }

    let [fbeModelKey, fbeModelValue] = this.getItem(0)
    for (let i = 0; i < fbeMapSize; i++) {
      let key = fbeModelKey.get()
      let value = fbeModelValue.get()
      values.set(key, value)
      fbeModelKey.fbeShift(fbeModelKey.fbeSize + fbeModelValue.fbeSize)
      fbeModelValue.fbeShift(fbeModelKey.fbeSize + fbeModelValue.fbeSize)
    }

    return values
  }

  /**
   * Set the map
   * @this {!FieldModelMap}
   * @param {!Map} values Map values
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    let [fbeModelKey, fbeModelValue] = this.resize(values.size)
    for (let [key, value] of values) {
      fbeModelKey.set(key)
      fbeModelKey.fbeShift(fbeModelKey.fbeSize + fbeModelValue.fbeSize)
      fbeModelValue.set(value)
      fbeModelValue.fbeShift(fbeModelKey.fbeSize + fbeModelValue.fbeSize)
    }
  }
}

exports.FieldModelMap = FieldModelMap
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModel()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding final model
 */
class FinalModel extends FieldModelBase {
  /**
   * Check if the value is valid
   * @this {!FinalModel}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    return Number.MAX_SAFE_INTEGER
  }
}

exports.FinalModel = FinalModel
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModel(const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _TYPE_ final model
 */
class FinalModel_NAME_ extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModel_NAME_}
   * @param {!_BASE_} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FinalModel_NAME_}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return _SIZE_
  }

  /**
   * Check if the value is valid
   * @this {!FinalModel_NAME_}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the value
   * @this {!FieldModel_NAME_}
   * @returns {!object} Result value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: _DEFAULTS_, size: 0 }
    }

    return { value: this.read_NAME_(this.fbeOffset), size: this.fbeSize }
  }

  /**
   * Set the value
   * @this {!FieldModel_NAME_}
   * @param {!_BASE_} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    this.write_NAME_(this.fbeOffset, value)
    return this.fbeSize
  }
}

exports.FinalModel_NAME_ = FinalModel_NAME_
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelDecimal()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding decimal final model
 */
class FinalModelDecimal extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModelDecimal}
   * @param {!Big} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FieldModelDecimal}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return 16
  }

  /**
   * Check if the decimal value is valid
   * @this {!FinalModelDecimal}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the decimal value
   * @this {!FieldModelDecimal}
   * @returns {!object} Result decimal value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: new Big(0), size: 0 }
    }

    // Read decimal parts
    let low = this.readUInt32(this.fbeOffset)
    let mid = this.readUInt32(this.fbeOffset + 4)
    let high = this.readUInt32(this.fbeOffset + 8)
    let flags = this.readUInt32(this.fbeOffset + 12)

    // Calculate decimal value
    let negative = (flags & 0x80000000) !== 0
    let scale = (flags & 0x7FFFFFFF) >> 16
    let result = new Big(0)
    result = result.add(new Big(high).mul('18446744073709551616'))
    result = result.add(new Big(mid).mul('4294967296'))
    result = result.add(new Big(low))
    result = result.div(Math.pow(10, scale))
    if (negative) {
      result.s = -1
    }

    return { value: result, size: this.fbeSize }
  }

  /**
   * Set the decimal value
   * @this {!FieldModelDecimal}
   * @param {!Big} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    // Extract decimal parts
    let negative = value.s < 0
    let scale = Math.max(0, value.c.length - 1 - value.e)
    let number = value.mul(Math.pow(10, scale)).abs()

    // Check for decimal scale overflow
    if ((scale < 0) || (scale > 28)) {
      // Value scale exceeds .NET Decimal limit of [0, 28]
      this.writeCount(this.fbeOffset, 0, this.fbeSize)
      return this.fbeSize
    }

    // Write unscaled value to bytes 0-11
    let index = 0
    while (number > 0) {
      // Check for decimal number overflow
      if (index > 11) {
        // Value too big for .NET Decimal (bit length is limited to [0, 96])
        this.writeCount(this.fbeOffset, 0, this.fbeSize)
        return this.fbeSize
      }
      let byte = parseInt(number.mod(256))
      this.writeByte(this.fbeOffset + index, byte)
      number = number.div(256).round(0, 0)
      index++
    }

    // Fill remaining bytes with zeros
    while (index < 12) {
      this.writeByte(this.fbeOffset + index, 0)
      index++
    }

    // Write scale at byte 14
    this.writeByte(this.fbeOffset + 14, scale)

    // Write signum at byte 15
    this.writeByte(this.fbeOffset + 15, (negative ? 0x80 : 0))
    return this.fbeSize
  }
}

exports.FinalModelDecimal = FinalModelDecimal
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelTimestamp()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding timestamp final model
 */
class FinalModelTimestamp extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModelTimestamp}
   * @param {!Date} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FieldModelTimestamp}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return 8
  }

  /**
   * Check if the timestamp value is valid
   * @this {!FinalModelTimestamp}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the timestamp value
   * @this {!FieldModelTimestamp}
   * @returns {!object} Result timestamp value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: new Date(0), size: 0 }
    }

    let nanoseconds = this.readUInt64(this.fbeOffset)
    return { value: new Date(Math.round(nanoseconds / 1000000)), size: this.fbeSize }
  }

  /**
   * Set the timestamp value
   * @this {!FieldModelTimestamp}
   * @param {!Date} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    let nanoseconds = UInt64.fromNumber(value.getTime()).mul(1000000)
    this.writeUInt64(this.fbeOffset, nanoseconds)
    return this.fbeSize
  }
}

exports.FinalModelTimestamp = FinalModelTimestamp
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelUUID()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding UUID final model
 */
class FinalModelUUID extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModelUUID}
   * @param {!UUID} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FieldModelUUID}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return 16
  }

  /**
   * Check if the UUID value is valid
   * @this {!FinalModelUUID}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the UUID value
   * @this {!FieldModelUUID}
   * @returns {!object} Result UUID value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: new UUID(), size: 0 }
    }

    return { value: new UUID(this.readBytes(this.fbeOffset, 16)), size: this.fbeSize }
  }

  /**
   * Set the UUID value
   * @this {!FieldModelUUID}
   * @param {!UUID} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    this.writeBytes(this.fbeOffset, value.data)
    return this.fbeSize
  }
}

exports.FinalModelUUID = FinalModelUUID
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelBytes()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding bytes final model
 */
class FinalModelBytes extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModelBytes}
   * @param {!Uint8Array} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return 4 + value.length
  }

  /**
   * Check if the bytes value is valid
   * @this {!FieldModelBytes}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeBytesSize = this.readUInt32(this.fbeOffset)
    if ((this._buffer.offset + this.fbeOffset + 4 + fbeBytesSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return 4 + fbeBytesSize
  }

  /**
   * Get the bytes value
   * @this {!FieldModelBytes}
   * @returns {!object} Result bytes value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return { value: new Uint8Array(0), size: 0 }
    }

    let fbeBytesSize = this.readUInt32(this.fbeOffset)
    console.assert(((this._buffer.offset + this.fbeOffset + 4 + fbeBytesSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4 + fbeBytesSize) > this._buffer.size) {
      return { value: new Uint8Array(0), size: 4 }
    }

    return { value: this.readBytes(this.fbeOffset + 4, fbeBytesSize), size: (4 + fbeBytesSize) }
  }

  /**
   * Set the bytes value
   * @this {!FieldModelBytes}
   * @param {!Uint8Array} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return 0
    }

    let fbeBytesSize = value.length
    console.assert(((this._buffer.offset + this.fbeOffset + 4 + fbeBytesSize) <= this._buffer.size), 'Model is broken!')
    if (((this._buffer.offset + this.fbeOffset + 4 + fbeBytesSize) > this._buffer.size)) {
      return 4
    }

    this.writeUInt32(this.fbeOffset, fbeBytesSize)
    this.writeBytes(this.fbeOffset + 4, value)
    return 4 + fbeBytesSize
  }
}

exports.FinalModelBytes = FinalModelBytes
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelString()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding string final model
 */
class FinalModelString extends FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModelString}
   * @param {!string} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return 4 + 3 * (value.length + 1)
  }

  /**
   * Check if the string value is valid
   * @this {!FieldModelString}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeStringSize = this.readUInt32(this.fbeOffset)
    if ((this._buffer.offset + this.fbeOffset + 4 + fbeStringSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return 4 + fbeStringSize
  }

  /**
   * Get the string value
   * @this {!FieldModelString}
   * @returns {!object} Result string value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return { value: '', size: 0 }
    }

    let fbeStringSize = this.readUInt32(this.fbeOffset)
    console.assert(((this._buffer.offset + this.fbeOffset + 4 + fbeStringSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4 + fbeStringSize) > this._buffer.size) {
      return { value: '', size: 4 }
    }

    return { value: this.readString(this.fbeOffset + 4, fbeStringSize), size: (4 + fbeStringSize) }
  }

  /**
   * Set the string value
   * @this {!FieldModelString}
   * @param {!string} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return 0
    }

    let fbeStringSize = utf8count(value)
    console.assert(((this._buffer.offset + this.fbeOffset + 4 + fbeStringSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4 + fbeStringSize) > this._buffer.size) {
      return 4
    }

    this.writeUInt32(this.fbeOffset, fbeStringSize)
    this.writeString(this.fbeOffset + 4, value, fbeStringSize)
    return 4 + fbeStringSize
  }
}

exports.FinalModelString = FinalModelString
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelOptional()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding optional final model
 */
class FinalModelOptional extends FinalModel {
  /**
   * Initialize optional final model with the given value final model and buffer
   * @param {!FinalModel} model Value final model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
    this._model.fbeOffset = 0
  }

  /**
   * Get the allocation size
   * @this {!FinalModelOptional}
   * @param {object} optional Optional value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (optional) {
    return 1 + ((optional != null) ? this.value.fbeAllocationSize(optional) : 0)
  }

  /**
   * Checks if the object contains a value
   * @this {!FinalModelOptional}
   * @returns {!boolean} Optional has value flag
   */
  get hasValue () {
    if ((this._buffer.offset + this.fbeOffset + 1) > this._buffer.size) {
      return false
    }

    let fbeHasValue = this.readUInt8(this.fbeOffset)
    return fbeHasValue !== 0
  }

  /**
   * Get the base final model value
   * @this {!FinalModelOptional}
   * @returns {!FinalModel} Base final model value
   */
  get value () {
    return this._model
  }

  /**
   * Check if the optional value is valid
   * @this {!FinalModelOptional}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 1) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeHasValue = this.readUInt8(this.fbeOffset)
    if (fbeHasValue === 0) {
      return 1
    }

    this._buffer.shift(this.fbeOffset + 1)
    let fbeResult = this.value.verify()
    this._buffer.unshift(this.fbeOffset + 1)
    return 1 + fbeResult
  }

  /**
   * Get the optional value
   * @this {!FinalModelOptional}
   * @returns {!object} Result optional value and its size
   */
  get () {
    console.assert(((this._buffer.offset + this.fbeOffset + 1) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 1) > this._buffer.size) {
      return { value: undefined, size: 0 }
    }

    if (!this.hasValue) {
      return { value: undefined, size: 1 }
    }

    this._buffer.shift(this.fbeOffset + 1)
    let optional = this.value.get()
    this._buffer.unshift(this.fbeOffset + 1)
    return { value: optional.value, size: (1 + optional.size) }
  }

  /**
   * Set the optional value
   * @this {!FinalModelOptional}
   * @param {object} optional Optional value
   * @returns {!number} Final model size
   */
  set (optional) {
    console.assert(((this._buffer.offset + this.fbeOffset + 1) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 1) > this._buffer.size) {
      return 0
    }

    let hasValue = (optional != null)
    this.writeUInt8(this.fbeOffset, (hasValue ? 1 : 0))
    if (!hasValue) {
      return 1
    }

    this._buffer.shift(this.fbeOffset + 1)
    let size = this.value.set(optional)
    this._buffer.unshift(this.fbeOffset + 1)
    return 1 + size
  }
}

exports.FinalModelOptional = FinalModelOptional
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelArray()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding array final model
 */
class FinalModelArray extends FinalModel {
  /**
   * Initialize array final model with the given value field model, buffer and array size
   * @param {!FinalModel} model Value final model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @constructor
   */
  constructor (model, buffer, offset, size) {
    super(buffer, offset)
    this._model = model
    this._size = size
  }

  /**
   * Get the allocation size
   * @this {!FinalModelArray}
   * @param {!Array} values Array values
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (values) {
    let size = 0
    for (let i = 0; i < Math.min(values.length, this._size); i++) {
      size += this._model.fbeAllocationSize(values[i])
    }
    return size
  }

  /**
   * Check if the array is valid
   * @this {!FinalModelArray}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let size = 0
    this._model.fbeOffset = this.fbeOffset
    for (let i = 0; i < this._size; i++) {
      let offset = this._model.verify()
      if (offset === Number.MAX_SAFE_INTEGER) {
        return Number.MAX_SAFE_INTEGER
      }
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }

  /**
   * Get the array
   * @this {!FinalModelArray}
   * @param {Array=} values Array values, defaults is []
   * @returns {!object} Result array values and their size
   */
  get (values = []) {
    values.length = 0

    console.assert(((this._buffer.offset + this.fbeOffset) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset) > this._buffer.size) {
      return { value: values, size: 0 }
    }

    let size = 0
    this._model.fbeOffset = this.fbeOffset
    for (let i = 0; i < this._size; i++) {
      let value = this._model.get()
      values.push(value.value)
      this._model.fbeShift(value.size)
      size += value.size
    }
    return { value: values, size: size }
  }

  /**
   * Set the array
   * @this {!FinalModelArray}
   * @param {!Array} values Array values
   * @returns {!number} Final model size
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset) > this._buffer.size) {
      return 0
    }

    let size = 0
    this._model.fbeOffset = this.fbeOffset
    for (let i = 0; i < Math.min(values.length, this._size); i++) {
      let offset = this._model.set(values[i])
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }
}

exports.FinalModelArray = FinalModelArray
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelVector()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding vector final model
 */
class FinalModelVector extends FinalModel {
  /**
   * Initialize vector final model with the given value final model and buffer
   * @param {!FinalModel} model Value final model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
  }

  /**
   * Get the allocation size
   * @this {!FinalModelVector}
   * @param {!Array} values Vector values
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (values) {
    let size = 0
    for (let value of values) {
      size += this._model.fbeAllocationSize(value)
    }
    return size
  }

  /**
   * Check if the vector is valid
   * @this {!FinalModelVector}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeVectorSize = this.readUInt32(this.fbeOffset)

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeVectorSize; i++) {
      let offset = this._model.verify()
      if (offset === Number.MAX_SAFE_INTEGER) {
        return Number.MAX_SAFE_INTEGER
      }
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }

  /**
   * Get the vector
   * @this {!FinalModelVector}
   * @param {Array=} values Vector values, defaults is []
   * @returns {!object} Result vector values and their size
   */
  get (values = []) {
    values.length = 0

    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return { value: values, size: 0 }
    }

    let fbeVectorSize = this.readUInt32(this.fbeOffset)
    if (fbeVectorSize === 0) {
      return { value: values, size: 4 }
    }

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeVectorSize; i++) {
      let value = this._model.get()
      values.push(value.value)
      this._model.fbeShift(value.size)
      size += value.size
    }
    return { value: values, size: size }
  }

  /**
   * Set the vector
   * @this {!FinalModelVector}
   * @param {!Array} values Vector values
   * @returns {!number} Final model size
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return 0
    }

    this.writeUInt32(this.fbeOffset, values.length)

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let value of values) {
      let offset = this._model.set(value)
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }
}

exports.FinalModelVector = FinalModelVector
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelSet()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding set final model
 */
class FinalModelSet extends FinalModel {
  /**
   * Initialize set final model with the given value final model and buffer
   * @param {!FinalModel} model Value final model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (model, buffer, offset) {
    super(buffer, offset)
    this._model = model
  }

  /**
   * Get the allocation size
   * @this {!FinalModelSet}
   * @param {!Set} values Set values
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (values) {
    let size = 0
    for (let value of values) {
      size += this._model.fbeAllocationSize(value)
    }
    return size
  }

  /**
   * Check if the set value is valid
   * @this {!FinalModelSet}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeSetSize = this.readUInt32(this.fbeOffset)

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeSetSize; i++) {
      let offset = this._model.verify()
      if (offset === Number.MAX_SAFE_INTEGER) {
        return Number.MAX_SAFE_INTEGER
      }
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }

  /**
   * Get the set value
   * @this {!FinalModelSet}
   * @param {Set=} values Set values, defaults is new Set()
   * @returns {!object} Result set values and their size
   */
  get (values = new Set()) {
    values.clear()

    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return { value: values, size: 0 }
    }

    let fbeSetSize = this.readUInt32(this.fbeOffset)
    if (fbeSetSize === 0) {
      return { value: values, size: 4 }
    }

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeSetSize; i++) {
      let value = this._model.get()
      values.add(value.value)
      this._model.fbeShift(value.size)
      size += value.size
    }
    return { value: values, size: size }
  }

  /**
   * Set the set value
   * @this {!FinalModelSet}
   * @param {!Set} values Set values
   * @returns {!number} Final model size
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return 0
    }

    this.writeUInt32(this.fbeOffset, values.size)

    let size = 4
    this._model.fbeOffset = this.fbeOffset + 4
    for (let value of values) {
      let offset = this._model.set(value)
      this._model.fbeShift(offset)
      size += offset
    }
    return size
  }
}

exports.FinalModelSet = FinalModelSet
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEFinalModelMap()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding map final model
 */
class FinalModelMap extends FinalModel {
  /**
   * Initialize map final model with the given key/value final models and buffer
   * @param {!FinalModel} modelKey Key final model
   * @param {!FinalModel} modelValue Value final model
   * @param {!ReadBuffer|!WriteBuffer} buffer Read/Write buffer
   * @param {!number} offset Offset
   * @constructor
   */
  constructor (modelKey, modelValue, buffer, offset) {
    super(buffer, offset)
    this._modelKey = modelKey
    this._modelValue = modelValue
  }

  /**
   * Get the allocation size
   * @this {!FinalModelMap}
   * @param {!Map} values Map values
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (values) {
    let size = 0
    for (let [key, value] of values) {
      size += this._modelKey.fbeAllocationSize(key)
      size += this._modelValue.fbeAllocationSize(value)
    }
    return size
  }

  /**
   * Check if the map is valid
   * @this {!FinalModelMap}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    let fbeMapSize = this.readUInt32(this.fbeOffset)

    let size = 4
    this._modelKey.fbeOffset = this.fbeOffset + 4
    this._modelValue.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeMapSize; i++) {
      let offsetKey = this._modelKey.verify()
      if (offsetKey === Number.MAX_SAFE_INTEGER) {
        return Number.MAX_SAFE_INTEGER
      }
      this._modelKey.fbeShift(offsetKey)
      this._modelValue.fbeShift(offsetKey)
      size += offsetKey
      let offsetValue = this._modelValue.verify()
      if (offsetValue === Number.MAX_SAFE_INTEGER) {
        return Number.MAX_SAFE_INTEGER
      }
      this._modelKey.fbeShift(offsetValue)
      this._modelValue.fbeShift(offsetValue)
      size += offsetValue
    }
    return size
  }

  /**
   * Get the map
   * @this {!FinalModelMap}
   * @param {Map=} values Map values, defaults is new Map()
   * @returns {!object} Result map values and their size
   */
  get (values = new Map()) {
    values.clear()

    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return { value: values, size: 0 }
    }

    let fbeMapSize = this.readUInt32(this.fbeOffset)
    if (fbeMapSize === 0) {
      return { value: values, size: 4 }
    }

    let size = 4
    this._modelKey.fbeOffset = this.fbeOffset + 4
    this._modelValue.fbeOffset = this.fbeOffset + 4
    for (let i = 0; i < fbeMapSize; i++) {
      let key = this._modelKey.get()
      this._modelKey.fbeShift(key.size)
      this._modelValue.fbeShift(key.size)
      size += key.size
      let value = this._modelValue.get()
      this._modelKey.fbeShift(value.size)
      this._modelValue.fbeShift(value.size)
      size += value.size
      values.set(key.value, value.value)
    }
    return { value: values, size: size }
  }

  /**
   * Set the map
   * @this {!FinalModelMap}
   * @param {!Map} values Map values
   * @returns {!number} Final model size
   */
  set (values) {
    console.assert(((this._buffer.offset + this.fbeOffset + 4) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + 4) > this._buffer.size) {
      return 0
    }

    this.writeUInt32(this.fbeOffset, values.size)

    let size = 4
    this._modelKey.fbeOffset = this.fbeOffset + 4
    this._modelValue.fbeOffset = this.fbeOffset + 4
    for (let [key, value] of values) {
      let offsetKey = this._modelKey.set(key)
      this._modelKey.fbeShift(offsetKey)
      this._modelValue.fbeShift(offsetKey)
      size += offsetKey
      let offsetValue = this._modelValue.set(value)
      this._modelKey.fbeShift(offsetValue)
      this._modelValue.fbeShift(offsetValue)
      size += offsetValue
    }
    return size
  }
}

exports.FinalModelMap = FinalModelMap
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBESender()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base sender
 */
class Sender {
  /**
   * Initialize sender with the given buffer and logging flag
   * @param {!WriteBuffer} buffer Write buffer, defaults is new WriteBuffer()
   * @param {boolean=} final Final protocol flag, defaults is false
   * @constructor
   */
  constructor (buffer = new WriteBuffer(), final = false) {
    this._buffer = buffer
    this._logging = false
    this._final = final
  }

  /**
   * Get the bytes buffer
   * @this {!Sender}
   * @returns {!WriteBuffer} Bytes buffer
   */
  get buffer () {
    return this._buffer
  }

  /**
   * Get the final protocol flag
   * @this {!Sender}
   * @returns {!boolean} Final protocol flag
   */
  get final () {
    return this._final
  }

  /**
   * Get the logging flag
   * @this {!Sender}
   * @returns {!boolean} Logging flag
   */
  get logging () {
    return this._logging
  }

  /**
   * Set the logging flag
   * @this {!Sender}
   * @param {!boolean} logging Logging flag
   */
  set logging (logging) {
    this._logging = logging
  }

  /**
   * Reset the sender buffer
   * @this {!Sender}
   */
  reset () {
    this._buffer.reset()
  }

  /**
   * Send serialized buffer.
   * Direct call of the method requires knowledge about internals of FBE models serialization.
   * Use it with care!
   * @this {!Sender}
   * @param {!number} serialized Serialized bytes
   * @returns {!number} Sent bytes
   */
  sendSerialized (serialized) {
    console.assert((serialized > 0), 'Invalid size of the serialized buffer!')
    if (serialized <= 0) {
      return 0
    }

    // Shift the send buffer
    this._buffer.shift(serialized)

    // Send the value
    let sent = this.onSend(this._buffer.buffer, 0, this._buffer.size)
    this._buffer.remove(0, sent)
    return sent
  }

  /**
   * Send message handler
   * @this {!Sender}
   * @param {!Uint8Array} buffer Buffer to send
   * @param {!number} offset Buffer offset
   * @param {!number} size Buffer size
   */
  onSend (buffer, offset, size) {
    console.assert(true, 'Abstract method call!')
    debugger // eslint-disable-line
    return 0
  }

  /**
   * Send log message handler
   * @this {!Sender}
   * @param {!string} message Log message
   */
  onSendLog (message) {}

  /**
   * Setup send message handler
   * @this {!Sender}
   * @param {!function} handler Send message handler
   */
  set onSendHandler (handler) { // eslint-disable-line
    this.onSend = handler
  }

  /**
   * Setup send log message handler
   * @this {!Sender}
   * @param {!function} handler Send log message handler
   */
  set onSendLogHandler (handler) { // eslint-disable-line
    this.onSendLog = handler
  }
}

exports.Sender = Sender
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEReceiver()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base receiver
 */
class Receiver {
  /**
   * Initialize receiver with the given buffer and logging flag
   * @param {!WriteBuffer} buffer Write buffer, defaults is new WriteBuffer()
   * @param {boolean=} final Final protocol flag, defaults is false
   * @constructor
   */
  constructor (buffer = new WriteBuffer(), final = false) {
    this._buffer = buffer
    this._logging = false
    this._final = final
  }

  /**
   * Get the bytes buffer
   * @this {!Receiver}
   * @returns {!WriteBuffer} Bytes buffer
   */
  get buffer () {
    return this._buffer
  }

  /**
   * Get the final protocol flag
   * @this {!Sender}
   * @returns {!boolean} Final protocol flag
   */
  get final () {
    return this._final
  }

  /**
   * Get the logging flag
   * @this {!Receiver}
   * @returns {!boolean} Logging flag
   */
  get logging () {
    return this._logging
  }

  /**
   * Set the logging flag
   * @this {!Receiver}
   * @param {!boolean} logging Logging flag
   */
  set logging (logging) {
    this._logging = logging
  }

  /**
   * Reset the receiver buffer
   * @this {!Receiver}
   */
  reset () {
    this._buffer.reset()
  }

  /**
   * Receive data
   * @this {!Receiver}
   * @param {!Uint8Array|!ReadBuffer|!WriteBuffer} buffer Buffer
   * @param {number=} offset Buffer offset, defaulfs is 0
   * @param {number=} size Buffer size, defaulfs is undefined
   */
  receive (buffer, offset = 0, size = undefined) {
    if (size == null) {
      size = buffer.length
    }

    console.assert(((offset + size) <= buffer.length), 'Invalid offset & size!')
    if ((offset + size) > buffer.length) {
      throw new Error('Invalid offset & size!')
    }

    if (size === 0) {
      return
    }

    if ((buffer instanceof ReadBuffer) || (buffer instanceof WriteBuffer)) {
      buffer = buffer.buffer
    }

    // Storage buffer
    let offset0 = this._buffer.offset
    let offset1 = this._buffer.size
    let size1 = this._buffer.size

    // Receive buffer
    let offset2 = 0
    let size2 = size

    // While receive buffer is available to handle...
    while (offset2 < size2) {
      let messageBuffer = null
      let messageOffset = 0
      let messageSize = 0

      // Try to receive message size
      let messageSizeCopied = false
      let messageSizeFound = false
      while (!messageSizeFound) {
        // Look into the storage buffer
        if (offset0 < size1) {
          let count = Math.min(size1 - offset0, 4)
          if (count === 4) {
            messageSizeCopied = true
            messageSizeFound = true
            messageSize = Receiver.readUInt32(this._buffer.buffer, this._buffer.offset + offset0)
            offset0 += 4
            break
          } else {
            // Fill remaining data from the receive buffer
            if (offset2 < size2) {
              count = Math.min(size2 - offset2, 4 - count)

              // Allocate and refresh the storage buffer
              this._buffer.allocate(count)
              size1 += count

              for (let i = 0; i < count; i++) {
                this._buffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
              }
              offset1 += count
              offset2 += count
              continue
            } else {
              break
            }
          }
        }

        // Look into the receive buffer
        if (offset2 < size2) {
          let count = Math.min(size2 - offset2, 4)
          if (count === 4) {
            messageSizeFound = true
            messageSize = Receiver.readUInt32(buffer, offset + offset2)
            offset2 += 4
            break
          } else {
            // Allocate and refresh the storage buffer
            this._buffer.allocate(count)
            size1 += count

            for (let i = 0; i < count; i++) {
              this._buffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
            }
            offset1 += count
            offset2 += count
            // noinspection UnnecessaryContinueJS
            continue
          }
        } else {
          break
        }
      }

      if (!messageSizeFound) {
        return
      }

      // Check the message full size
      let minSize = this._final ? (4 + 4) : (4 + 4 + 4 + 4)
      console.assert((messageSize >= minSize), 'Invalid receive data!')
      if (messageSize < minSize) {
        return
      }

      // Try to receive message body
      let messageFound = false
      while (!messageFound) {
        // Look into the storage buffer
        if (offset0 < size1) {
          let count = Math.min(size1 - offset0, messageSize - 4)
          if (count === (messageSize - 4)) {
            messageFound = true
            messageBuffer = this._buffer.buffer
            messageOffset = offset0 - 4
            offset0 += messageSize - 4
            break
          } else {
            // Fill remaining data from the receive buffer
            if (offset2 < size2) {
              // Copy message size into the storage buffer
              if (!messageSizeCopied) {
                // Allocate and refresh the storage buffer
                this._buffer.allocate(4)
                size1 += 4

                Receiver.writeUInt32(this._buffer.buffer, this._buffer.offset + offset0, messageSize)
                offset0 += 4
                offset1 += 4

                messageSizeCopied = true
              }

              count = Math.min(size2 - offset2, messageSize - 4 - count)

              // Allocate and refresh the storage buffer
              this._buffer.allocate(count)
              size1 += count

              for (let i = 0; i < count; i++) {
                this._buffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
              }
              offset1 += count
              offset2 += count
              continue
            } else {
              break
            }
          }
        }

        // Look into the receive buffer
        if (offset2 < size2) {
          let count = Math.min(size2 - offset2, messageSize - 4)
          if (!messageSizeCopied && (count === (messageSize - 4))) {
            messageFound = true
            messageBuffer = buffer
            messageOffset = offset + offset2 - 4
            offset2 += messageSize - 4
            break
          } else {
            // Copy message size into the storage buffer
            if (!messageSizeCopied) {
              // Allocate and refresh the storage buffer
              this._buffer.allocate(4)
              size1 += 4

              Receiver.writeUInt32(this._buffer.buffer, this._buffer.offset + offset0, messageSize)
              offset0 += 4
              offset1 += 4

              messageSizeCopied = true
            }

            // Allocate and refresh the storage buffer
            this._buffer.allocate(count)
            size1 += count

            for (let i = 0; i < count; i++) {
              this._buffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
            }
            offset1 += count
            offset2 += count
            // noinspection UnnecessaryContinueJS
            continue
          }
        } else {
          break
        }
      }

      if (!messageFound) {
        // Copy message size into the storage buffer
        if (!messageSizeCopied) {
          // Allocate and refresh the storage buffer
          this._buffer.allocate(4)
          size1 += 4

          Receiver.writeUInt32(this._buffer.buffer, this._buffer.offset + offset0, messageSize)
          offset0 += 4
          offset1 += 4

          messageSizeCopied = true
        }
        return
      }

      // noinspection JSUnusedLocalSymbols
      let fbeStructSize // eslint-disable-line
      let fbeStructType

      // Read the message parameters
      if (this._final) {
        fbeStructSize = Receiver.readUInt32(messageBuffer, messageOffset)
        fbeStructType = Receiver.readUInt32(messageBuffer, messageOffset + 4)
      } else {
        let fbeStructOffset = Receiver.readUInt32(messageBuffer, messageOffset + 4)
        // noinspection JSUnusedLocalSymbols
        fbeStructSize = Receiver.readUInt32(messageBuffer, messageOffset + fbeStructOffset) // eslint-disable-line
        fbeStructType = Receiver.readUInt32(messageBuffer, messageOffset + fbeStructOffset + 4)
      }

      // Handle the message
      this.onReceive(fbeStructType, messageBuffer, messageOffset, messageSize)

      // Reset the storage buffer
      this._buffer.reset()

      // Refresh the storage buffer
      offset0 = this._buffer.offset
      offset1 = this._buffer.size
      size1 = this._buffer.size
    }
  }

  /**
   * Receive message handler
   * @this {!Receiver}
   * @param {!number} type Message type
   * @param {!Uint8Array} buffer Buffer to send
   * @param {!number} offset Buffer offset
   * @param {!number} size Buffer size
   * @returns {!boolean} Success flag
   */
  onReceive (type, buffer, offset, size) {
    console.assert(true, 'Abstract method call!')
    debugger // eslint-disable-line
    return false
  }

  /**
   * Receive log message handler
   * @this {!Receiver}
   * @param {!string} message Log message
   */
  onReceiveLog (message) {}

  /**
   * Setup receive log message handler
   * @this {!Receiver}
   * @param {!function} handler Receive log message handler
   */
  set onReceiveLogHandler (handler) { // eslint-disable-line
    this.onReceiveLog = handler
  }

  // Buffer I/O methods

  /**
   * Check the buffer offset bounds
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   */
  static checkOffset (buffer, offset, size) {
    if (((offset % 1) !== 0) || (offset < 0)) {
      throw new RangeError('Invalid offset!')
    }
    if ((offset + size) > buffer.length) {
      throw new RangeError('Out of bounds!')
    }
  }

  /**
   * Check the value range and its buffer offset bounds
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @param {!number} value Value
   * @param {!number} min Min value
   * @param {!number} max Max value
   */
  static checkValue (buffer, offset, size, value, min, max) {
    this.checkOffset(buffer, offset, size)
    if ((value < min) || (value > max)) {
      throw new RangeError('Value is out of bounds!')
    }
  }

  /**
   * Read UInt32 value from the model buffer
   * @this {!Model}
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @returns {!number} UInt32 value
   */
  static readUInt32 (buffer, offset) {
    offset = offset >>> 0
    Receiver.checkOffset(buffer, offset, 4)
    return (
      (buffer[offset + 0] << 0) |
      (buffer[offset + 1] << 8) |
      (buffer[offset + 2] << 16)) +
      (buffer[offset + 3] * 0x1000000)
  }

  /**
   * Write UInt32 value into the model buffer
   * @this {!Model}
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} value Value
   */
  static writeUInt32 (buffer, offset, value) {
    value = +value
    Receiver.checkValue(buffer, offset, 4, value, 0, 0xFFFFFFFF)
    buffer[offset + 3] = (value >>> 24)
    buffer[offset + 2] = (value >>> 16)
    buffer[offset + 1] = (value >>> 8)
    buffer[offset + 0] = (value & 0xFF)
  }
}

exports.Receiver = Receiver
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEClient()
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding base client
 */
class Client {
  /**
   * Initialize client with the given buffers and logging flag
   * @param {!WriteBuffer} sendBuffer Send buffer, defaults is new WriteBuffer()
   * @param {!WriteBuffer} receiveBuffer Receive buffer, defaults is new WriteBuffer()
   * @param {boolean=} final Final protocol flag, defaults is false
   * @constructor
   */
  constructor (sendBuffer = new WriteBuffer(), receiveBuffer = new WriteBuffer(), final = false) {
    this._sendBuffer = sendBuffer
    this._receiveBuffer = receiveBuffer
    this._logging = false
    this._final = final
  }

  /**
   * Get the send bytes buffer
   * @this {!Sender}
   * @returns {!WriteBuffer} Send bytes buffer
   */
  get sendBuffer () {
    return this._sendBuffer
  }

  /**
   * Get the receive bytes buffer
   * @this {!Sender}
   * @returns {!WriteBuffer} Receive bytes buffer
   */
  get receiveBuffer () {
    return this._receiveBuffer
  }

  /**
   * Get the final protocol flag
   * @this {!Sender}
   * @returns {!boolean} Final protocol flag
   */
  get final () {
    return this._final
  }

  /**
   * Get the logging flag
   * @this {!Sender}
   * @returns {!boolean} Logging flag
   */
  get logging () {
    return this._logging
  }

  /**
   * Set the logging flag
   * @this {!Sender}
   * @param {!boolean} logging Logging flag
   */
  set logging (logging) {
    this._logging = logging
  }

  /**
   * Reset the sender and receive buffers
   * @this {!Sender}
   */
  reset () {
    this._sendBuffer.reset()
    this._receiveBuffer.reset()
  }

  /**
   * Send serialized buffer.
   * Direct call of the method requires knowledge about internals of FBE models serialization.
   * Use it with care!
   * @this {!Sender}
   * @param {!number} serialized Serialized bytes
   * @returns {!number} Sent bytes
   */
  sendSerialized (serialized) {
    console.assert((serialized > 0), 'Invalid size of the serialized buffer!')
    if (serialized <= 0) {
      return 0
    }

    // Shift the send buffer
    this._sendBuffer.shift(serialized)

    // Send the value
    let sent = this.onSend(this._sendBuffer.buffer, 0, this._sendBuffer.size)
    this._sendBuffer.remove(0, sent)
    return sent
  }

  /**
   * Send message handler
   * @this {!Sender}
   * @param {!Uint8Array} buffer Buffer to send
   * @param {!number} offset Buffer offset
   * @param {!number} size Buffer size
   */
  onSend (buffer, offset, size) {
    console.assert(true, 'Abstract method call!')
    debugger // eslint-disable-line
    return 0
  }

  /**
   * Send log message handler
   * @this {!Sender}
   * @param {!string} message Log message
   */
  onSendLog (message) {}

  /**
   * Setup send message handler
   * @this {!Sender}
   * @param {!function} handler Send message handler
   */
  set onSendHandler (handler) { // eslint-disable-line
    this.onSend = handler
  }

  /**
   * Setup send log message handler
   * @this {!Sender}
   * @param {!function} handler Send log message handler
   */
  set onSendLogHandler (handler) { // eslint-disable-line
    this.onSendLog = handler
  }

  /**
   * Receive data
   * @this {!Receiver}
   * @param {!Uint8Array|!ReadBuffer|!WriteBuffer} buffer Buffer
   * @param {number=} offset Buffer offset, defaulfs is 0
   * @param {number=} size Buffer size, defaulfs is undefined
   */
  receive (buffer, offset = 0, size = undefined) {
    if (size == null) {
      size = buffer.length
    }

    console.assert(((offset + size) <= buffer.length), 'Invalid offset & size!')
    if ((offset + size) > buffer.length) {
      throw new Error('Invalid offset & size!')
    }

    if (size === 0) {
      return
    }

    if ((buffer instanceof ReadBuffer) || (buffer instanceof WriteBuffer)) {
      buffer = buffer.buffer
    }

    // Storage buffer
    let offset0 = this._receiveBuffer.offset
    let offset1 = this._receiveBuffer.size
    let size1 = this._receiveBuffer.size

    // Receive buffer
    let offset2 = 0
    let size2 = size

    // While receive buffer is available to handle...
    while (offset2 < size2) {
      let messageBuffer = null
      let messageOffset = 0
      let messageSize = 0

      // Try to receive message size
      let messageSizeCopied = false
      let messageSizeFound = false
      while (!messageSizeFound) {
        // Look into the storage buffer
        if (offset0 < size1) {
          let count = Math.min(size1 - offset0, 4)
          if (count === 4) {
            messageSizeCopied = true
            messageSizeFound = true
            messageSize = Receiver.readUInt32(this._receiveBuffer.buffer, this._receiveBuffer.offset + offset0)
            offset0 += 4
            break
          } else {
            // Fill remaining data from the receive buffer
            if (offset2 < size2) {
              count = Math.min(size2 - offset2, 4 - count)

              // Allocate and refresh the storage buffer
              this._receiveBuffer.allocate(count)
              size1 += count

              for (let i = 0; i < count; i++) {
                this._receiveBuffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
              }
              offset1 += count
              offset2 += count
              continue
            } else {
              break
            }
          }
        }

        // Look into the receive buffer
        if (offset2 < size2) {
          let count = Math.min(size2 - offset2, 4)
          if (count === 4) {
            messageSizeFound = true
            messageSize = Receiver.readUInt32(buffer, offset + offset2)
            offset2 += 4
            break
          } else {
            // Allocate and refresh the storage buffer
            this._receiveBuffer.allocate(count)
            size1 += count

            for (let i = 0; i < count; i++) {
              this._receiveBuffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
            }
            offset1 += count
            offset2 += count
            // noinspection UnnecessaryContinueJS
            continue
          }
        } else {
          break
        }
      }

      if (!messageSizeFound) {
        return
      }

      // Check the message full size
      let minSize = this._final ? (4 + 4) : (4 + 4 + 4 + 4)
      console.assert((messageSize >= minSize), 'Invalid receive data!')
      if (messageSize < minSize) {
        return
      }

      // Try to receive message body
      let messageFound = false
      while (!messageFound) {
        // Look into the storage buffer
        if (offset0 < size1) {
          let count = Math.min(size1 - offset0, messageSize - 4)
          if (count === (messageSize - 4)) {
            messageFound = true
            messageBuffer = this._receiveBuffer.buffer
            messageOffset = offset0 - 4
            offset0 += messageSize - 4
            break
          } else {
            // Fill remaining data from the receive buffer
            if (offset2 < size2) {
              // Copy message size into the storage buffer
              if (!messageSizeCopied) {
                // Allocate and refresh the storage buffer
                this._receiveBuffer.allocate(4)
                size1 += 4

                Receiver.writeUInt32(this._receiveBuffer.buffer, this._receiveBuffer.offset + offset0, messageSize)
                offset0 += 4
                offset1 += 4

                messageSizeCopied = true
              }

              count = Math.min(size2 - offset2, messageSize - 4 - count)

              // Allocate and refresh the storage buffer
              this._receiveBuffer.allocate(count)
              size1 += count

              for (let i = 0; i < count; i++) {
                this._receiveBuffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
              }
              offset1 += count
              offset2 += count
              continue
            } else {
              break
            }
          }
        }

        // Look into the receive buffer
        if (offset2 < size2) {
          let count = Math.min(size2 - offset2, messageSize - 4)
          if (!messageSizeCopied && (count === (messageSize - 4))) {
            messageFound = true
            messageBuffer = buffer
            messageOffset = offset + offset2 - 4
            offset2 += messageSize - 4
            break
          } else {
            // Copy message size into the storage buffer
            if (!messageSizeCopied) {
              // Allocate and refresh the storage buffer
              this._receiveBuffer.allocate(4)
              size1 += 4

              Receiver.writeUInt32(this._receiveBuffer.buffer, this._receiveBuffer.offset + offset0, messageSize)
              offset0 += 4
              offset1 += 4

              messageSizeCopied = true
            }

            // Allocate and refresh the storage buffer
            this._receiveBuffer.allocate(count)
            size1 += count

            for (let i = 0; i < count; i++) {
              this._receiveBuffer.buffer[offset1 + i] = buffer[offset + offset2 + i]
            }
            offset1 += count
            offset2 += count
            // noinspection UnnecessaryContinueJS
            continue
          }
        } else {
          break
        }
      }

      if (!messageFound) {
        // Copy message size into the storage buffer
        if (!messageSizeCopied) {
          // Allocate and refresh the storage buffer
          this._receiveBuffer.allocate(4)
          size1 += 4

          Receiver.writeUInt32(this._receiveBuffer.buffer, this._receiveBuffer.offset + offset0, messageSize)
          offset0 += 4
          offset1 += 4

          messageSizeCopied = true
        }
        return
      }

      // noinspection JSUnusedLocalSymbols
      let fbeStructSize // eslint-disable-line
      let fbeStructType

      // Read the message parameters
      if (this._final) {
        fbeStructSize = Receiver.readUInt32(messageBuffer, messageOffset)
        fbeStructType = Receiver.readUInt32(messageBuffer, messageOffset + 4)
      } else {
        let fbeStructOffset = Receiver.readUInt32(messageBuffer, messageOffset + 4)
        // noinspection JSUnusedLocalSymbols
        fbeStructSize = Receiver.readUInt32(messageBuffer, messageOffset + fbeStructOffset) // eslint-disable-line
        fbeStructType = Receiver.readUInt32(messageBuffer, messageOffset + fbeStructOffset + 4)
      }

      // Handle the message
      this.onReceive(fbeStructType, messageBuffer, messageOffset, messageSize)

      // Reset the storage buffer
      this._receiveBuffer.reset()

      // Refresh the storage buffer
      offset0 = this._receiveBuffer.offset
      offset1 = this._receiveBuffer.size
      size1 = this._receiveBuffer.size
    }
  }

  /**
   * Receive message handler
   * @this {!Receiver}
   * @param {!number} type Message type
   * @param {!Uint8Array} buffer Buffer to send
   * @param {!number} offset Buffer offset
   * @param {!number} size Buffer size
   * @returns {!boolean} Success flag
   */
  onReceive (type, buffer, offset, size) {
    console.assert(true, 'Abstract method call!')
    debugger // eslint-disable-line
    return false
  }

  /**
   * Receive log message handler
   * @this {!Receiver}
   * @param {!string} message Log message
   */
  onReceiveLog (message) {}

  /**
   * Setup receive log message handler
   * @this {!Receiver}
   * @param {!function} handler Receive log message handler
   */
  set onReceiveLogHandler (handler) { // eslint-disable-line
    this.onReceiveLog = handler
  }

  // Buffer I/O methods

  /**
   * Check the buffer offset bounds
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   */
  static checkOffset (buffer, offset, size) {
    if (((offset % 1) !== 0) || (offset < 0)) {
      throw new RangeError('Invalid offset!')
    }
    if ((offset + size) > buffer.length) {
      throw new RangeError('Out of bounds!')
    }
  }

  /**
   * Check the value range and its buffer offset bounds
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} size Size
   * @param {!number} value Value
   * @param {!number} min Min value
   * @param {!number} max Max value
   */
  static checkValue (buffer, offset, size, value, min, max) {
    this.checkOffset(buffer, offset, size)
    if ((value < min) || (value > max)) {
      throw new RangeError('Value is out of bounds!')
    }
  }

  /**
   * Read UInt32 value from the model buffer
   * @this {!Model}
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @returns {!number} UInt32 value
   */
  static readUInt32 (buffer, offset) {
    offset = offset >>> 0
    Receiver.checkOffset(buffer, offset, 4)
    return (
      (buffer[offset + 0] << 0) |
      (buffer[offset + 1] << 8) |
      (buffer[offset + 2] << 16)) +
      (buffer[offset + 3] * 0x1000000)
  }

  /**
   * Write UInt32 value into the model buffer
   * @this {!Model}
   * @param {!Uint8Array} buffer Buffer
   * @param {!number} offset Offset
   * @param {!number} value Value
   */
  static writeUInt32 (buffer, offset, value) {
    value = +value
    Receiver.checkValue(buffer, offset, 4, value, 0, 0xFFFFFFFF)
    buffer[offset + 3] = (value >>> 24)
    buffer[offset + 2] = (value >>> 16)
    buffer[offset + 1] = (value >>> 8)
    buffer[offset + 0] = (value & 0xFF)
  }
}

exports.Client = Client
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFBEJson()
{
    std::string code = R"CODE(
/**
 * Converts Map instance to object datatype
 * @param {!Map} map Map to convert
 * @returns {!object} Object
 */
let MapToObject = function (map) {
  let obj = {}
  map.forEach((value, key) => { obj[key] = value })
  return obj
}

exports.MapToObject = MapToObject

/**
 * Convert object instance to Map datatype
 * @param {!object} obj Object to convert
 * @returns {!Map} Map
 */
let ObjectToMap = function (obj) {
  let map = new Map()
  Object.keys(obj).forEach(key => { map.set(key, obj[key]) })
  return map
}

exports.ObjectToMap = ObjectToMap
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateImports(const std::shared_ptr<Package>& p)
{
    // Generate common import
    WriteLine();
    WriteLineIndent("const util = require('util')");
    WriteLine();
    WriteLineIndent("const big = require('./big')");
    WriteLineIndent("const int64 = require('./int64')");
    WriteLineIndent("const uuid = require('./uuid')");
    WriteLine();
    WriteLineIndent("const Big = big.Big // eslint-disable-line");
    WriteLineIndent("const Int64 = int64.Int64 // eslint-disable-line");
    WriteLineIndent("const UInt64 = int64.UInt64 // eslint-disable-line");
    WriteLineIndent("const UUID = uuid.UUID // eslint-disable-line");

    // Generate FBE import
    WriteLine();
    WriteLineIndent("const fbe = require('./fbe')");

    // Generate packages import
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent("const " + *import + " = require('./" + *import + "')");
}

void GeneratorJavaScript::GeneratePackage(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate common files
    GenerateBig(output);
    GenerateInt64(output);
    GenerateUUID(output);
    GenerateIEEE754(output);
    GenerateUTF8(output);
    GenerateFBE(output);

    // Generate the output file
    output /= *p->name + ".js";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate eslint deflations
    WriteLine();
    WriteLineIndent("/* eslint-disable prefer-const,no-loss-of-precision */");
    WriteLineIndent("'use strict'");

    // Generate package imports
    GenerateImports(p);

    // Generate namespace
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(child_e);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(child_f);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(child_s);
    }

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(p);

        // Generate sender & receiver
        GenerateSender(p, false);
        GenerateReceiver(p, false);
        GenerateProxy(p, false);
        GenerateClient(p, false);
        if (Final())
        {
            GenerateSender(p, true);
            GenerateReceiver(p, true);
            GenerateClient(p, true);
        }
    }

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorJavaScript::GenerateEnum(const std::shared_ptr<EnumType>& e)
{
    // Generate enum begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *e->name + " enum");
    WriteLineIndent(" */");
    WriteLineIndent("class " + *e->name + " {");
    Indent(1);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Javascript enums are limited to 32-bit integer numbers
    if (enum_type == "int64")
        enum_type = "int32";
    else if (enum_type == "uint64")
        enum_type = "uint32";

    // Generate enum constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize enum with a given value");
    WriteLineIndent(" * @param {" + *e->name + "|number|Int64|UInt64=} value Enum value, defaults is 0");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (value = 0) {");
    Indent(1);
    WriteLineIndent("if (value instanceof " + *e->name + ") {");
    Indent(1);
    WriteLineIndent("this.value = value.value");
    Indent(-1);
    WriteLineIndent("} else {");
    Indent(1);
    WriteLineIndent("this.value = value");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum eq() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Is this enum equal to other one?");
    WriteLineIndent(" * @this {!" + *e->name + "}");
    WriteLineIndent(" * @param {!" + *e->name + "} other Other enum");
    WriteLineIndent(" * @returns {boolean} Equal result");
    WriteLineIndent(" */");
    WriteLineIndent("eq (other) {");
    Indent(1);
    WriteLineIndent("if (!(other instanceof " + *e->name + ")) {");
    Indent(1);
    WriteLineIndent("throw new TypeError('Instance of " + *e->name + " is required!')");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("return this.value === other.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum valueOf() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get enum value");
    WriteLineIndent(" * @this {!" + *e->name + "}");
    WriteLineIndent(" * @returns {!number|!Int64|!UInt64} Enum value");
    WriteLineIndent(" */");
    WriteLineIndent("valueOf () {");
    Indent(1);
    WriteLineIndent("return this.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum toString() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert enum to string");
    WriteLineIndent(" * @this {!" + *e->name + "}");
    WriteLineIndent(" * @returns {!string} Enum value string");
    WriteLineIndent(" */");
    WriteLineIndent("toString () {");
    Indent(1);
    if (e->body && !e->body->values.empty())
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
        {
            WriteLineIndent("if (this.value === " + *e->name + "." + *(*it)->name + ".value) {");
            Indent(1);
            WriteLineIndent("return '" + *(*it)->name + "'");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLineIndent("return '<unknown>'");
    }
    else
        WriteLineIndent("return '<empty>'");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum [util.inspect.custom]() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Inspect enum");
    WriteLineIndent(" * @this {!" + *e->name + "}");
    WriteLineIndent(" * @returns {!string} Enum value string");
    WriteLineIndent(" */");
    WriteLineIndent("[util.inspect.custom] () {");
    Indent(1);
    WriteLineIndent("return this.toString()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum toJSON() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert enum to JSON");
    WriteLineIndent(" * @this {!" + *e->name + "}");
    WriteLineIndent(" * @returns {!number} Enum value for JSON");
    WriteLineIndent(" */");
    WriteLineIndent("toJSON () {");
    Indent(1);
    WriteLineIndent("return this.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum fromObject() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create enum from object value");
    WriteLineIndent(" * @param {!number} other Object value");
    WriteLineIndent(" * @returns {!" + *e->name + "} Created enum");
    WriteLineIndent(" */");
    WriteLineIndent("static fromObject (other) {");
    Indent(1);
    WriteLineIndent("return new " + *e->name + "(other)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum end
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum values
    if (e->body && !e->body->values.empty())
    {
        WriteLine();
        int index = 0;
        std::string last = ConvertEnumConstant(*e->name, "int32", "0");
        for (const auto& value : e->body->values)
        {
            WriteLineIndent("// noinspection PointlessArithmeticExpressionJS");
            WriteIndent(*e->name + "." + *value->name + " = new " + *e->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(*e->name, enum_type, *value->value->constant);
                    Write(last + " + " + std::to_string(index++));
                }
                else if (value->value->reference && !value->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(*e->name, "", *value->value->reference);
                    Write(last);
                }
            }
            else
                Write(last + " + " + std::to_string(index++));
            Write(")");
            WriteLine();
        }
    }

    // Generate enum exports
    WriteLine();
    WriteLineIndent("exports." + *e->name + " = " + *e->name);

    // Generate enum field model
    GenerateEnumFieldModel(e);

    // Generate enum final model
    if (Final())
        GenerateEnumFinalModel(e);
}

void GeneratorJavaScript::GenerateEnumFieldModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _ENUM_NAME_ field model
 */
class FieldModel_ENUM_NAME_ extends fbe.FieldModel {
  /**
   * Get the field size
   * @this {!FieldModel_ENUM_NAME_}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return _ENUM_SIZE_
  }

  /**
   * Get the value
   * @this {!FieldModel_ENUM_NAME_}
   * @param {_ENUM_NAME_=} defaults Default value, defaults is new _ENUM_NAME_()
   * @returns {!_ENUM_NAME_} Result value
   */
  get (defaults = new _ENUM_NAME_()) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    return new _ENUM_NAME_(this.read_ENUM_TYPE_(this.fbeOffset)_ENUM_READ_SUFFIX_)
  }

  /**
   * Set the value
   * @this {!FieldModel_ENUM_NAME_}
   * @param {!_ENUM_NAME_} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    this.write_ENUM_TYPE_(this.fbeOffset, _ENUM_WRITE_PREFIX_value.value_ENUM_WRITE_SUFFIX_)
  }
}

exports.FieldModel_ENUM_NAME_ = FieldModel_ENUM_NAME_
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_read_suffix = "";
    std::string enum_write_prefix = "";
    std::string enum_write_suffix = "";

    // Javascript enums are limited to 32-bit integer numbers
    if (enum_type == "int64")
    {
        enum_read_suffix = ".toNumber()";
        enum_write_prefix = "Int64.fromNumber(";
        enum_write_suffix = ")";
    }
    else if (enum_type == "uint64")
    {
        enum_read_suffix = ".toNumber()";
        enum_write_prefix = "UInt64.fromNumber(";
        enum_write_suffix = ")";
    }

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_READ_SUFFIX_"), enum_read_suffix);
    code = std::regex_replace(code, std::regex("_ENUM_WRITE_PREFIX_"), enum_write_prefix);
    code = std::regex_replace(code, std::regex("_ENUM_WRITE_SUFFIX_"), enum_write_suffix);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateEnumFinalModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _ENUM_NAME_ final model
 */
class FinalModel_ENUM_NAME_ extends fbe.FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModel_ENUM_NAME_}
   * @param {!_ENUM_NAME_} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FieldModel_ENUM_NAME_}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return _ENUM_SIZE_
  }

  /**
   * Check if the value is valid
   * @this {!FinalModel_ENUM_NAME_}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the value
   * @this {!FieldModel_ENUM_NAME_}
   * @returns {!object} Result value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: new _ENUM_NAME_(), size: 0 }
    }

    return { value: new _ENUM_NAME_(this.read_ENUM_TYPE_(this.fbeOffset)_ENUM_READ_SUFFIX_), size: this.fbeSize }
  }

  /**
   * Set the value
   * @this {!FieldModel_ENUM_NAME_}
   * @param {!_ENUM_NAME_} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    this.write_ENUM_TYPE_(this.fbeOffset, _ENUM_WRITE_PREFIX_value.value_ENUM_WRITE_SUFFIX_)
    return this.fbeSize
  }
}

exports.FinalModel_ENUM_NAME_ = FinalModel_ENUM_NAME_
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_read_suffix = "";
    std::string enum_write_prefix = "";
    std::string enum_write_suffix = "";

    // Javascript enums are limited to 32-bit integer numbers
    if (enum_type == "int64")
    {
        enum_read_suffix = ".toNumber()";
        enum_write_prefix = "Int64.fromNumber(";
        enum_write_suffix = ")";
    }
    else if (enum_type == "uint64")
    {
        enum_read_suffix = ".toNumber()";
        enum_write_prefix = "UInt64.fromNumber(";
        enum_write_suffix = ")";
    }

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_READ_SUFFIX_"), enum_read_suffix);
    code = std::regex_replace(code, std::regex("_ENUM_WRITE_PREFIX_"), enum_write_prefix);
    code = std::regex_replace(code, std::regex("_ENUM_WRITE_SUFFIX_"), enum_write_suffix);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFlags(const std::shared_ptr<FlagsType>& f)
{
    // Generate flags begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *f->name + " flags");
    WriteLineIndent(" */");
    WriteLineIndent("class " + *f->name + " {");
    Indent(1);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Javascript flags are limited to 32-bit integer numbers
    if (flags_type == "int64")
        flags_type = "int32";
    else if (flags_type == "uint64")
        flags_type = "uint32";

    // Generate flags constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize flags with a given value");
    WriteLineIndent(" * @param {" + *f->name + "|number|Int64|UInt64=} value Flags value, defaults is 0");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (value = 0) {");
    Indent(1);
    WriteLineIndent("if (value instanceof " + *f->name + ") {");
    Indent(1);
    WriteLineIndent("this.value = value.value");
    Indent(-1);
    WriteLineIndent("} else {");
    Indent(1);
    WriteLineIndent("this.value = value");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags hasFlags() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check for the given flags set state");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @param {!" + *f->name + "|!number|!Int64|!UInt64} flags Flags");
    WriteLineIndent(" * @returns {!boolean} Flags set state");
    WriteLineIndent(" */");
    WriteLineIndent("hasFlags (flags) {");
    Indent(1);
    WriteLineIndent("if (flags instanceof " + *f->name + ") {");
    Indent(1);
    WriteLineIndent("flags = flags.value");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("return ((this.value & flags) !== 0) && ((this.value & flags) === flags)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags setFlags() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the given flags");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @param {!" + *f->name + "|!number|!Int64|!UInt64} flags Flags");
    WriteLineIndent(" */");
    WriteLineIndent("setFlags (flags) {");
    Indent(1);
    WriteLineIndent("if (flags instanceof " + *f->name + ") {");
    Indent(1);
    WriteLineIndent("flags = flags.value");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("this.value |= flags");
    WriteLineIndent("return this");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags removeFlags() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Remove the given flags");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @param {!" + *f->name + "|!number|!Int64|!UInt64} flags Flags");
    WriteLineIndent(" */");
    WriteLineIndent("removeFlags (flags) {");
    Indent(1);
    WriteLineIndent("if (flags instanceof " + *f->name + ") {");
    Indent(1);
    WriteLineIndent("flags = flags.value");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("this.value &= ~flags");
    WriteLineIndent("return this");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags eq() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Is this flags equal to other one?");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @param {!" + *f->name + "} other Other flags");
    WriteLineIndent(" * @returns {boolean} Equal result");
    WriteLineIndent(" */");
    WriteLineIndent("eq (other) {");
    Indent(1);
    WriteLineIndent("if (!(other instanceof " + *f->name + ")) {");
    Indent(1);
    WriteLineIndent("throw new TypeError('Instance of " + *f->name + " is required!')");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("return this.value === other.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags valueOf() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get flags value");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @returns {!number|!Int64|!UInt64} Flags value");
    WriteLineIndent(" */");
    WriteLineIndent("valueOf () {");
    Indent(1);
    WriteLineIndent("return this.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags toString() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert flags to string");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @returns {!string} Flags value string");
    WriteLineIndent(" */");
    WriteLineIndent("toString () {");
    Indent(1);
    WriteLineIndent("let result = ''");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("let first = true");
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            WriteLineIndent("if ((this.value & " + *f->name + "." + *(*it)->name + ".value) && ((this.value & " + *f->name + "." + *(*it)->name + ".value) === " + *f->name + "." + *(*it)->name + ".value)) {");
            Indent(1);
            WriteLineIndent("result += (first ? '' : '|') + '" + *(*it)->name + "'");
            WriteLineIndent("// noinspection JSUnusedAssignment");
            WriteLineIndent("first = false");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return result");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags [util.inspect.custom]() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Inspect flags");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @returns {!string} Flags value string");
    WriteLineIndent(" */");
    WriteLineIndent("[util.inspect.custom] () {");
    Indent(1);
    WriteLineIndent("return this.toString()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags toJSON() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert flags to JSON");
    WriteLineIndent(" * @this {!" + *f->name + "}");
    WriteLineIndent(" * @returns {!number} Flags value for JSON");
    WriteLineIndent(" */");
    WriteLineIndent("toJSON () {");
    Indent(1);
    WriteLineIndent("return this.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags fromFlags() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create flags from number flags representation");
    WriteLineIndent(" * @param {!number} flags Number flags representation");
    WriteLineIndent(" * @returns {!" + *f->name + "} Created flags");
    WriteLineIndent(" */");
    WriteLineIndent("static fromFlags (flags) {");
    Indent(1);
    WriteLineIndent("return new " + *f->name + "(flags)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags fromObject() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create flags from object value");
    WriteLineIndent(" * @param {!number} other Object value");
    WriteLineIndent(" * @returns {!" + *f->name + "} Created flags");
    WriteLineIndent(" */");
    WriteLineIndent("static fromObject (other) {");
    Indent(1);
    WriteLineIndent("return new " + *f->name + "(other)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags end
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags values
    if (f->body && !f->body->values.empty())
    {
        WriteLine();
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("// noinspection PointlessArithmeticExpressionJS");
            WriteIndent(*f->name + "." + *value->name + " = new " + *f->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(ConvertEnumConstant(*f->name, flags_type, *value->value->constant));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(ConvertEnumConstant(*f->name, "", *value->value->reference));
            }
            Write(")");
            WriteLine();
        }
    }

    // Generate flags exports
    WriteLine();
    WriteLineIndent("exports." + *f->name + " = " + *f->name);

    // Generate flags field model
    GenerateFlagsFieldModel(f);

    // Generate flags final model
    if (Final())
        GenerateFlagsFinalModel(f);
}

void GeneratorJavaScript::GenerateFlagsFieldModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _FLAGS_NAME_ field model
 */
class FieldModel_FLAGS_NAME_ extends fbe.FieldModel {
  /**
   * Get the field size
   * @this {!FieldModel_FLAGS_NAME_}
   * @returns {!number} Field size
   */
  get fbeSize () {
    return _FLAGS_SIZE_
  }

  /**
   * Get the value
   * @this {!FieldModel_FLAGS_NAME_}
   * @param {_FLAGS_NAME_=} defaults Default value, defaults is new _FLAGS_NAME_()
   * @returns {!_FLAGS_NAME_} Result value
   */
  get (defaults = new _FLAGS_NAME_()) {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return defaults
    }

    return new _FLAGS_NAME_(this.read_FLAGS_TYPE_(this.fbeOffset)_FLAGS_READ_SUFFIX_)
  }

  /**
   * Set the value
   * @this {!FieldModel_FLAGS_NAME_}
   * @param {!_FLAGS_NAME_} value Value
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return
    }

    this.write_FLAGS_TYPE_(this.fbeOffset, _FLAGS_WRITE_PREFIX_value.value_FLAGS_WRITE_SUFFIX_)
  }
}

exports.FieldModel_FLAGS_NAME_ = FieldModel_FLAGS_NAME_
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_read_suffix = "";
    std::string flags_write_prefix = "";
    std::string flags_write_suffix = "";

    // Javascript flags are limited to 32-bit integer numbers
    if (flags_type == "int64")
    {
        flags_read_suffix = ".toNumber()";
        flags_write_prefix = "Int64.fromNumber(";
        flags_write_suffix = ")";
    }
    else if (flags_type == "uint64")
    {
        flags_read_suffix = ".toNumber()";
        flags_write_prefix = "UInt64.fromNumber(";
        flags_write_suffix = ")";
    }

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_READ_SUFFIX_"), flags_read_suffix);
    code = std::regex_replace(code, std::regex("_FLAGS_WRITE_PREFIX_"), flags_write_prefix);
    code = std::regex_replace(code, std::regex("_FLAGS_WRITE_SUFFIX_"), flags_write_suffix);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateFlagsFinalModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(
/**
 * Fast Binary Encoding _FLAGS_NAME_ final model
 */
class FinalModel_FLAGS_NAME_ extends fbe.FinalModel {
  /**
   * Get the allocation size
   * @this {!FinalModel_FLAGS_NAME_}
   * @param {!_FLAGS_NAME_} value Value
   * @returns {!number} Allocation size
   */
  fbeAllocationSize (value) {
    return this.fbeSize
  }

  /**
   * Get the final size
   * @this {!FieldModel_FLAGS_NAME_}
   * @returns {!number} Final size
   */
  get fbeSize () {
    return _FLAGS_SIZE_
  }

  /**
   * Check if the value is valid
   * @this {!FinalModel_FLAGS_NAME_}
   * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error
   */
  verify () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return Number.MAX_SAFE_INTEGER
    }

    return this.fbeSize
  }

  /**
   * Get the value
   * @this {!FieldModel_FLAGS_NAME_}
   * @returns {!object} Result value and its size
   */
  get () {
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return { value: new _FLAGS_NAME_(), size: 0 }
    }

    return { value: new _FLAGS_NAME_(this.read_FLAGS_TYPE_(this.fbeOffset)_FLAGS_READ_SUFFIX_), size: this.fbeSize }
  }

  /**
   * Set the value
   * @this {!FieldModel_FLAGS_NAME_}
   * @param {!_FLAGS_NAME_} value Value
   * @returns {!number} Final model size
   */
  set (value) {
    console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')
    if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {
      return 0
    }

    this.write_FLAGS_TYPE_(this.fbeOffset, _FLAGS_WRITE_PREFIX_value.value_FLAGS_WRITE_SUFFIX_)
    return this.fbeSize
  }
}

exports.FinalModel_FLAGS_NAME_ = FinalModel_FLAGS_NAME_
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_read_suffix = "";
    std::string flags_write_prefix = "";
    std::string flags_write_suffix = "";

    // Javascript flags are limited to 32-bit integer numbers
    if (flags_type == "int64")
    {
        flags_read_suffix = ".toNumber()";
        flags_write_prefix = "Int64.fromNumber(";
        flags_write_suffix = ")";
    }
    else if (flags_type == "uint64")
    {
        flags_read_suffix = ".toNumber()";
        flags_write_prefix = "UInt64.fromNumber(";
        flags_write_suffix = ")";
    }

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_READ_SUFFIX_"), flags_read_suffix);
    code = std::regex_replace(code, std::regex("_FLAGS_WRITE_PREFIX_"), flags_write_prefix);
    code = std::regex_replace(code, std::regex("_FLAGS_WRITE_SUFFIX_"), flags_write_suffix);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJavaScript::GenerateStruct(const std::shared_ptr<StructType>& s)
{
    bool first;

    // Generate struct begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *s->name + " struct");
    WriteLineIndent(" */");
    WriteIndent("class " + *s->name);
    if (s->base && !s->base->empty())
        WriteIndent(" extends " + ConvertTypeName(*s->base, false));
    WriteLineIndent(" {");
    Indent(1);

    // Generate struct constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        WriteLineIndent("/**");
        WriteLineIndent(" * Initialize struct");
        if (s->base && !s->base->empty())
            WriteLineIndent(" * @param {!" + ConvertTypeName(*s->base, false) + "=} parent");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(" * @param {" + std::string(field->optional ? "" : "!") + ConvertTypeName(*field) + "=} " + *field->name);
        WriteLineIndent(" * @constructor");
        WriteLineIndent(" */");
        WriteIndent("constructor (");
        if (s->base && !s->base->empty())
        {
            Write("parent = new " + ConvertTypeName(*s->base, false) + "()");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "arg" : ", arg") + *field->name + " = " + ConvertDefault(*field));
                first = false;
            }
        }
        WriteLine(") {");
        Indent(1);
        if (s->base && !s->base->empty())
        {
            WriteLineIndent("super()");
            WriteLineIndent("super.copy(parent)");
        }
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this." + *field->name + " = arg" + *field->name);
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }

    // Generate struct copy() method
    WriteLineIndent("/**");
    WriteLineIndent(" * Copy struct (shallow copy)");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} other Other struct");
    WriteLineIndent(" * @returns {!" + *s->name + "} This struct");
    WriteLineIndent(" */");
    WriteLineIndent("copy (other) {");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("super.copy(other)");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("if (other." + *field->name + " != null) {");
            Indent(1);
            if (field->array || field->vector || field->list)
            {
                WriteLineIndent("this." + *field->name + " = []");
                WriteLineIndent("for (let item of other." + *field->name + ") {");
                Indent(1);
                WriteLineIndent("if (item != null) {");
                Indent(1);
                WriteLineIndent("let tempItem");
                CopyValueToVariable(*field->type, "item", "tempItem", field->optional);
                WriteLineIndent("this." + *field->name + ".push(tempItem)");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("this." + *field->name + ".push(undefined)");
                Indent(-1);
                WriteLineIndent("}");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("this." + *field->name + " = new Set()");
                WriteLineIndent("for (let item of other." + *field->name + ") {");
                Indent(1);
                WriteLineIndent("if (item != null) {");
                Indent(1);
                WriteLineIndent("let tempItem");
                CopyValueToVariable(*field->type, "item", "tempItem", field->optional);
                WriteLineIndent("this." + *field->name + ".add(tempItem)");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("this." + *field->name + ".add(undefined)");
                Indent(-1);
                WriteLineIndent("}");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map || field->hash)
            {
                WriteLineIndent("this." + *field->name + " = new Map()");
                WriteLineIndent("Object.keys(other." + *field->name + ").forEach(key => {");
                Indent(1);
                WriteLineIndent("if (key != null) {");
                Indent(1);
                WriteLineIndent("let tempKey");
                CopyValueToVariable(*field->key, "key", "tempKey", false);
                WriteLineIndent("if (other." + *field->name + "[key] != null) {");
                Indent(1);
                WriteLineIndent("let tempValue");
                CopyValueToVariable(*field->type, "other." + *field->name + "[key]", "tempValue", field->optional);
                WriteLineIndent("this." + *field->name + ".set(tempKey, tempValue)");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("this." + *field->name + ".set(tempKey, undefined)");
                Indent(-1);
                WriteLineIndent("}");
                Indent(-1);
                WriteLineIndent("}");
                Indent(-1);
                WriteLineIndent("})");
            }
            else
                CopyValueToVariable(*field->type, "other." + *field->name, "this." + *field->name, field->optional);
            Indent(-1);
            WriteLineIndent("} else {");
            Indent(1);
            WriteLineIndent("this." + *field->name + " = undefined");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return this");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct clone() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Clone struct (deep clone)");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!" + *s->name + "} Cloned struct");
    WriteLineIndent(" */");
    WriteLineIndent("clone () {");
    Indent(1);
    WriteLineIndent("// Serialize the struct to the FBE stream");
    WriteLineIndent("let writer = new " + *s->name + "Model(new fbe.WriteBuffer())");
    WriteLineIndent("writer.serialize(this)");
    WriteLine();
    WriteLineIndent("// Deserialize the struct from the FBE stream");
    WriteLineIndent("let reader = new " + *s->name + "Model(new fbe.ReadBuffer())");
    WriteLineIndent("reader.attachBuffer(writer.buffer)");
    WriteLineIndent("return reader.deserialize().value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct eq() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Is this struct equal to other one?");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} other Other struct");
    WriteLineIndent(" * @returns {boolean} Equal result");
    WriteLineIndent(" */");
    WriteLineIndent("eq (other) {");
    Indent(1);
    WriteLineIndent("if (!(other instanceof " + *s->name + ")) {");
    Indent(1);
    WriteLineIndent("throw new TypeError('Instance of " + *s->name + " is required!')");
    Indent(-1);
    WriteLineIndent("}");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("// noinspection RedundantIfStatementJS");
        WriteLineIndent("if (!super.eq(other)) {");
        Indent(1);
        WriteLineIndent("return false");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("// noinspection RedundantIfStatementJS");
                if (IsJavaScriptType(*field->type))
                {
                    WriteLineIndent("if (this." + *field->name + " !== other." + *field->name + ") {");
                    Indent(1);
                    WriteLineIndent("return false");
                    Indent(-1);
                    WriteLineIndent("}");
                }
                else
                {
                    WriteLineIndent("if (this." + *field->name + " != null) {");
                    Indent(1);
                    WriteLineIndent("if ((other." + *field->name + " == null) || !this." + *field->name + ".eq(other." + *field->name + ")) {");
                    Indent(1);
                    WriteLineIndent("return false");
                    Indent(-1);
                    WriteLineIndent("}");
                    Indent(-1);
                    WriteLineIndent("} else if (other." + *field->name + " != null) {");
                    Indent(1);
                    WriteLineIndent("return false");
                    Indent(-1);
                    WriteLineIndent("}");
                }
            }
        }
    }
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags toString() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert struct to string");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!string} Struct value string");
    WriteLineIndent(" */");
    WriteLineIndent("toString () {");
    Indent(1);
    WriteLineIndent("let result = ''");
    WriteLineIndent("result += '" + *s->name + "('");
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result += super.toString()");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("result += '" + std::string(first ? "" : ",") + *field->name + "='");
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("result += '***'");
            else if (field->array || field->vector)
            {
                WriteLineIndent("if (this." + *field->name + " != null) {");
                Indent(1);
                WriteLineIndent("let first = true");
                WriteLineIndent("result += '[' + this." + *field->name + ".length + ']['");
                WriteLineIndent("for (let item of this." + *field->name + ") {");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("result += ']'");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("result += '[0][]'");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->list)
            {
                WriteLineIndent("if (this." + *field->name + " != null) {");
                Indent(1);
                WriteLineIndent("let first = true");
                WriteLineIndent("result += '[' + this." + *field->name + ".length + ']<'");
                WriteLineIndent("for (let item of this." + *field->name + ") {");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("result += '>'");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("result += '[0]<>'");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("if (this." + *field->name + " != null) {");
                Indent(1);
                WriteLineIndent("let first = true");
                WriteLineIndent("result += '[' + this." + *field->name + ".size + ']{'");
                WriteLineIndent("for (let item of this." + *field->name + ") {");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("result += '}'");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("result += '[0]{}'");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map)
            {
                WriteLineIndent("if (this." + *field->name + " != null) {");
                Indent(1);
                WriteLineIndent("let first = true");
                WriteLineIndent("result += '[' + this." + *field->name + ".size + ']<{'");
                WriteLineIndent("for (let [key, value] of this." + *field->name + ") {");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", false, true);
                WriteLineIndent("result += '->'");
                WriteOutputStreamValue(*field->type, "value", field->optional, false);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("result += '}>'");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("result += '[0]<{}>'");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->hash)
            {
                WriteLineIndent("if (this." + *field->name + " != null) {");
                Indent(1);
                WriteLineIndent("let first = true");
                WriteLineIndent("result += '[' + this." + *field->name + ".size + '][{'");
                WriteLineIndent("for (let [key, value] of this." + *field->name + ") {");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", false, true);
                WriteLineIndent("result += '->'");
                WriteOutputStreamValue(*field->type, "value", field->optional, false);
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("result += '}]'");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                WriteLineIndent("result += '[0][{}]'");
                Indent(-1);
                WriteLineIndent("}");
            }
            else
                WriteOutputStreamValue(*field->type, "this." + *field->name, field->optional, false);
            first = false;
        }
    }
    WriteLineIndent("result += ')'");
    WriteLineIndent("return result");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct [util.inspect.custom]() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Inspect struct");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!string} Struct value string");
    WriteLineIndent(" */");
    WriteLineIndent("[util.inspect.custom] () {");
    Indent(1);
    WriteLineIndent("return this.toString()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct toJSON() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert struct to JSON");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!object} Struct value for JSON");
    WriteLineIndent(" */");
    WriteLineIndent("toJSON () {");
    Indent(1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("let parent = super.toJSON()");
        WriteLineIndent("let current = {");
    }
    else
        WriteLineIndent("return {");
    Indent(1);
    if (s->body)
    {
        for (size_t i = 0; i < s->body->fields.size(); ++i)
        {
            bool last = (i == (s->body->fields.size() - 1));
            auto field = s->body->fields[i];
            if (field->array || field->vector || field->list || field->set)
                WriteLineIndent(*field->name + ": ((this." + *field->name + " != null) ? Array.from(this." + *field->name + ", item => " + ConvertVariable(*field->type, "item", field->optional) + ") : null)" + std::string(last ? "" : ","));
            else if (field->map || field->hash)
                WriteLineIndent(*field->name + ": ((this." + *field->name + " != null) ? fbe.MapToObject(new Map(Array.from(this." + *field->name + ", item => [" + ConvertVariable(*field->key, "item[0]", false) + ", " + ConvertVariable(*field->type, "item[1]", field->optional) + "]))) : null)" + std::string(last ? "" : ","));
            else
                WriteLineIndent(*field->name + ": " + ConvertVariable(*field->type, "this." + *field->name, field->optional) + std::string(last ? "" : ","));

        }
    }
    Indent(-1);
    WriteLineIndent("}");
    if (s->base && !s->base->empty())
        WriteLineIndent("return { ...parent, ...current }");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct fromJSON() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Convert JSON to struct");
    WriteLineIndent(" * @param {!string} json JSON string");
    WriteLineIndent(" * @returns {!object} Struct value for JSON");
    WriteLineIndent(" */");
    WriteLineIndent("static fromJSON (json) {");
    Indent(1);
    WriteLineIndent("return " + *s->name + ".fromObject(JSON.parse(json))");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct fromObject() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create struct from object value");
    WriteLineIndent(" * @param {!" + *s->name + "} other Object value");
    WriteLineIndent(" * @returns {!" + *s->name + "} Created struct");
    WriteLineIndent(" */");
    WriteLineIndent("static fromObject (other) {");
    Indent(1);
    WriteLineIndent("return new " + *s->name + "().copy(other)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct FBE type property
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the FBE type");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} FBE type");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeType () {");
    Indent(1);
    WriteLineIndent("return " + *s->name + ".fbeType");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the FBE type (static)");
    WriteLineIndent(" * @this {!" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} FBE type");
    WriteLineIndent(" */");
    WriteLineIndent("static get fbeType () {");
    Indent(1);
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("return " + ConvertTypeName(*s->base, false) + ".fbeType");
    else
        WriteLineIndent("return " + std::to_string(s->type));
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct exports
    WriteLine();
    WriteLineIndent("exports." + *s->name + " = " + *s->name);

    // Generate struct field model
    GenerateStructFieldModel(s);

    // Generate struct model
    GenerateStructModel(s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(s);
        GenerateStructModelFinal(s);
    }
}

void GeneratorJavaScript::GenerateStructFieldModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Fast Binary Encoding " + *s->name + " field model");
    WriteLineIndent(" */");
    WriteLineIndent("class FieldModel" + *s->name + " extends fbe.FieldModel {");
    Indent(1);

    // Generate struct field model constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("/**");
        WriteLineIndent(" * Initialize field model with the given buffer and offset");
        WriteLineIndent(" * @param {!fbe.ReadBuffer|!fbe.WriteBuffer} buffer Buffer");
        WriteLineIndent(" * @param {!number} offset Offset");
        WriteLineIndent(" * @constructor");
        WriteLineIndent(" */");
        WriteLineIndent("constructor (buffer, offset) {");
        Indent(1);
        WriteLineIndent("super(buffer, offset)");
        std::string prev_offset("4");
        std::string prev_size("4");
        if (s->base && !s->base->empty())
        {
            WriteLineIndent("this._parent = new " + ConvertTypeFieldName(*s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ")");
            prev_offset = "this._parent.fbeOffset";
            prev_size = "this._parent.fbeBody - 4 - 4";
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLineIndent("this._" + *field->name + " = new " + ConvertTypeFieldInitialization(*field, prev_offset + " + " + prev_size, false));
                prev_offset = "this._" + *field->name + ".fbeOffset";
                prev_size = "this._" + *field->name + ".fbeSize";
            }
        }
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }

    // Generate struct field model accessors
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("/**");
        WriteLineIndent(" * Get the " + *s->base + " field model");
        WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
        WriteLineIndent(" * @returns {!" + ConvertTypeFieldName(*s->base, false) + "} " + *s->base + " field model");
        WriteLineIndent(" */");
        WriteLineIndent("get parent () {");
        Indent(1);
        WriteLineIndent("return this._parent");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("/**");
            WriteLineIndent(" * Get the " + *field->name + " field model");
            WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
            WriteLineIndent(" * @returns {!" + ConvertTypeFieldDeclaration(*field, false) + "} " + *field->name + " field model");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *field->name + " () {");
            Indent(1);
            WriteLineIndent("return this._" + *field->name);
            Indent(-1);
            WriteLineIndent("}");
            WriteLine();
        }
    }

    // Generate struct field model FBE properties
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the field size");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field size");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeSize () {");
    Indent(1);
    WriteLineIndent("return 4");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the field body size");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field body size");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeBody () {");
    Indent(1);
    WriteIndent("return 4 + 4");
    if (s->base && !s->base->empty())
        Write(" + this.parent.fbeBody - 4 - 4");
    if (s->body)
        for (const auto& field : s->body->fields)
            Write(" + this." + *field->name + ".fbeSize");
    WriteLine();
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the field extra size");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field extra size");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeExtra () {");
    Indent(1);
    WriteLineIndent("if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructOffset = this.readUInt32(this.fbeOffset)");
    WriteLineIndent("if ((fbeStructOffset === 0) || ((this._buffer.offset + fbeStructOffset + 4) > this._buffer.size)) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this._buffer.shift(fbeStructOffset)");
    WriteLine();
    WriteIndent("let fbeResult = this.fbeBody");
    Indent(1);
    if (s->base && !s->base->empty())
        Write(" + this.parent.fbeExtra");
    if (s->body)
        for (const auto& field : s->body->fields)
            Write(" + this." + *field->name + ".fbeExtra");
    WriteLine();
    Indent(-1);
    WriteLine();
    WriteLineIndent("this._buffer.unshift(fbeStructOffset)");
    WriteLine();
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the field type");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field type");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeType () {");
    Indent(1);
    WriteLineIndent("return FieldModel" + *s->name + ".fbeType");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the field type (static)");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field type");
    WriteLineIndent(" */");
    WriteLineIndent("static get fbeType () {");
    Indent(1);
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("return " + ConvertTypeFieldName(*s->base, false) + ".fbeType");
    else
        WriteLineIndent("return " + std::to_string(s->type));
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model verify() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct value is valid");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!boolean} fbeVerifyType Verify model type flag, defaults is true");
    WriteLineIndent(" * @returns {!boolean} Field model valid state");
    WriteLineIndent(" */");
    WriteLineIndent("verify (fbeVerifyType = true) {");
    Indent(1);
    WriteLineIndent("if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {");
    Indent(1);
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructOffset = this.readUInt32(this.fbeOffset)");
    WriteLineIndent("if ((fbeStructOffset === 0) || ((this._buffer.offset + fbeStructOffset + 4 + 4) > this._buffer.size)) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.readUInt32(fbeStructOffset)");
    WriteLineIndent("if (fbeStructSize < (4 + 4)) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructType = this.readUInt32(fbeStructOffset + 4)");
    WriteLineIndent("if (fbeVerifyType && (fbeStructType !== this.fbeType)) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this._buffer.shift(fbeStructOffset)");
    WriteLineIndent("let fbeResult = this.verifyFields(fbeStructSize)");
    WriteLineIndent("this._buffer.unshift(fbeStructOffset)");
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model verifyFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct fields are valid");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!number} fbeStructSize FBE struct size");
    WriteLineIndent(" * @returns {!boolean} Field model valid state");
    WriteLineIndent(" */");
    WriteLineIndent("verifyFields (fbeStructSize) {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("let fbeCurrentSize = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + this.parent.fbeBody - 4 - 4) > fbeStructSize) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
            WriteLineIndent("if (!this.parent.verifyFields(fbeStructSize)) {");
            Indent(1);
            WriteLineIndent("return false");
            Indent(-1);
            WriteLineIndent("}");
            WriteLineIndent("// noinspection JSUnusedAssignment");
            WriteLineIndent("fbeCurrentSize += this.parent.fbeBody - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + this." + *field->name + ".fbeSize) > fbeStructSize) {");
                Indent(1);
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("if (!this." + *field->name + ".verify()) {");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("// noinspection JSUnusedAssignment");
                WriteLineIndent("fbeCurrentSize += this." + *field->name + ".fbeSize");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getBegin() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct value (begin phase)");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("getBegin () {");
    Indent(1);
    WriteLineIndent("if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructOffset = this.readUInt32(this.fbeOffset)");
    WriteLineIndent("console.assert((fbeStructOffset > 0) && ((this._buffer.offset + fbeStructOffset + 4 + 4) <= this._buffer.size), 'Model is broken!')");
    WriteLineIndent("if ((fbeStructOffset === 0) || ((this._buffer.offset + fbeStructOffset + 4 + 4) > this._buffer.size)) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.readUInt32(fbeStructOffset)");
    WriteLineIndent("console.assert((fbeStructSize >= (4 + 4)), 'Model is broken!')");
    WriteLineIndent("if (fbeStructSize < (4 + 4)) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this._buffer.shift(fbeStructOffset)");
    WriteLineIndent("return fbeStructOffset");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getEnd() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct value (end phase)");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!number} fbeBegin Field model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("getEnd (fbeBegin) {");
    Indent(1);
    WriteLineIndent("this._buffer.unshift(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model get() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct value");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue Default value, defaults is new " + *s->name + "()");
    WriteLineIndent(" * @returns {!" + *s->name + "} " + *s->name + " value");
    WriteLineIndent(" */");
    WriteLineIndent("get (fbeValue = new " + *s->name + "()) {");
    Indent(1);
    WriteLineIndent("let fbeBegin = this.getBegin()");
    WriteLineIndent("if (fbeBegin === 0) {");
    Indent(1);
    WriteLineIndent("return fbeValue");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.readUInt32(0)");
    WriteLineIndent("this.getFields(fbeValue, fbeStructSize)");
    WriteLineIndent("this.getEnd(fbeBegin)");
    WriteLineIndent("return fbeValue");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct fields values");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" * @param {!number} fbeStructSize Struct size");
    WriteLineIndent(" */");
    WriteLineIndent("getFields (fbeValue, fbeStructSize) {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("let fbeCurrentSize = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + this.parent.fbeBody - 4 - 4) <= fbeStructSize) {");
            Indent(1);
            WriteLineIndent("this.parent.getFields(fbeValue, fbeStructSize)");
            Indent(-1);
            WriteLineIndent("}");
            WriteLineIndent("// noinspection JSUnusedAssignment");
            WriteLineIndent("fbeCurrentSize += this.parent.fbeBody - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + this." + *field->name + ".fbeSize) <= fbeStructSize) {");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("this." + *field->name + ".get(fbeValue." + *field->name + ")");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = this." + *field->name + ".get(" + (field->value ? ConvertConstant(*field->type, *field->value, field->optional) : "") + ")");
                Indent(-1);
                WriteLineIndent("} else {");
                Indent(1);
                if (field->array || field->vector || field->list)
                    WriteLineIndent("fbeValue." + *field->name + ".length = 0");
                else if (field->set || field->map || field->hash)
                    WriteLineIndent("fbeValue." + *field->name + ".clear()");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + ConvertDefault(*field));
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("// noinspection JSUnusedAssignment");
                WriteLineIndent("fbeCurrentSize += this." + *field->name + ".fbeSize");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setBegin() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct value (begin phase)");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Field model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("setBegin () {");
    Indent(1);
    WriteLineIndent("console.assert(((this._buffer.offset + this.fbeOffset + this.fbeSize) <= this._buffer.size), 'Model is broken!')");
    WriteLineIndent("if ((this._buffer.offset + this.fbeOffset + this.fbeSize) > this._buffer.size) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.fbeBody");
    WriteLineIndent("let fbeStructOffset = this._buffer.allocate(fbeStructSize) - this._buffer.offset");
    WriteLineIndent("console.assert((fbeStructOffset > 0) && ((this._buffer.offset + fbeStructOffset + fbeStructSize) <= this._buffer.size), 'Model is broken!')");
    WriteLineIndent("if ((fbeStructOffset <= 0) || ((this._buffer.offset + fbeStructOffset + fbeStructSize) > this._buffer.size)) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this.writeUInt32(this.fbeOffset, fbeStructOffset)");
    WriteLineIndent("this.writeUInt32(fbeStructOffset, fbeStructSize)");
    WriteLineIndent("this.writeUInt32(fbeStructOffset + 4, this.fbeType)");
    WriteLine();
    WriteLineIndent("this._buffer.shift(fbeStructOffset)");
    WriteLineIndent("return fbeStructOffset");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setEnd() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct value (end phase)");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!number} fbeBegin Field model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("setEnd (fbeBegin) {");
    Indent(1);
    WriteLineIndent("this._buffer.unshift(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model set() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct value");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" */");
    WriteLineIndent("set (fbeValue) {");
    Indent(1);
    WriteLineIndent("let fbeBegin = this.setBegin()");
    WriteLineIndent("if (fbeBegin === 0) {");
    Indent(1);
    WriteLineIndent("return");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this.setFields(fbeValue)");
    WriteLineIndent("this.setEnd(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct fields values");
    WriteLineIndent(" * @this {!FieldModel" + *s->name + "}");
    WriteLineIndent(" * @param {" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" */");
    WriteLineIndent("setFields (fbeValue) {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("this.parent.setFields(fbeValue)");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this." + *field->name + ".set(fbeValue." + *field->name + ")");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model exports
    WriteLine();
    WriteLineIndent("exports.FieldModel" + *s->name + " = FieldModel" + *s->name);
}

void GeneratorJavaScript::GenerateStructModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct model begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent(" */");
    WriteLineIndent("class " + *s->name + "Model extends fbe.Model {");
    Indent(1);

    // Generate struct model constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize model with the given buffer");
    WriteLineIndent(" * @param {!fbe.ReadBuffer|!fbe.WriteBuffer} buffer Read/Write buffer, defaults is new fbe.WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (buffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(buffer)");
    WriteLineIndent("this._model = new FieldModel" + *s->name + "(this.buffer, 4)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model accessor
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the " + *s->name + " model");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!FieldModel" + *s->name + "} model " + *s->name + " model");
    WriteLineIndent(" */");
    WriteLineIndent("get model () {");
    Indent(1);
    WriteLineIndent("return this._model");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the model size");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!number} Model size");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeSize () {");
    Indent(1);
    WriteLineIndent("return this.model.fbeSize + this.model.fbeExtra");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the model type");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!number} Model type");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeType () {");
    Indent(1);
    WriteLineIndent("return " + *s->name + "Model.fbeType");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the model type (static)");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!number} Model type");
    WriteLineIndent(" */");
    WriteLineIndent("static get fbeType () {");
    Indent(1);
    WriteLineIndent("return FieldModel" + *s->name + ".fbeType");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct value is valid");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!boolean} Model valid state");
    WriteLineIndent(" */");
    WriteLineIndent("verify () {");
    Indent(1);
    WriteLineIndent("if ((this.buffer.offset + this.model.fbeOffset - 4) > this.buffer.size) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeFullSize = this.readUInt32(this.model.fbeOffset - 4)");
    WriteLineIndent("if (fbeFullSize < this.model.fbeSize) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("return this.model.verify()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createBegin() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create a new model (begin phase)");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @returns {!number} Model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("createBegin () {");
    Indent(1);
    WriteLineIndent("return this.buffer.allocate(4 + this.model.fbeSize)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createEnd() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Create a new model (end phase)");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @param {!number} fbeBegin Model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("createEnd (fbeBegin) {");
    Indent(1);
    WriteLineIndent("let fbeEnd = this.buffer.size");
    WriteLineIndent("let fbeFullSize = fbeEnd - fbeBegin");
    WriteLineIndent("this.writeUInt32(this.model.fbeOffset - 4, fbeFullSize)");
    WriteLineIndent("return fbeFullSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model serialize() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Serialize the struct value");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value");
    WriteLineIndent(" * @return {!number} Model begin offset");
    WriteLineIndent(" */");
    WriteLineIndent("serialize (value) {");
    Indent(1);
    WriteLineIndent("let fbeBegin = this.createBegin()");
    WriteLineIndent("this.model.set(value)");
    WriteLineIndent("return this.createEnd(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model deserialize() methods
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Deserialize the struct value");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value, defaults is new " + *s->name + "()");
    WriteLineIndent(" * @return {!object} Deserialized " + *s->name + " value and its size");
    WriteLineIndent(" */");
    WriteLineIndent("deserialize (value = new " + *s->name + "()) {");
    Indent(1);
    WriteLineIndent("if ((this.buffer.offset + this.model.fbeOffset - 4) > this.buffer.size) {");
    Indent(1);
    WriteLineIndent("return { value: new " + *s->name + "(), size: 0 }");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeFullSize = this.readUInt32(this.model.fbeOffset - 4)");
    WriteLineIndent("console.assert((fbeFullSize >= this.model.fbeSize), 'Model is broken!')");
    WriteLineIndent("if (fbeFullSize < this.model.fbeSize) {");
    Indent(1);
    WriteLineIndent("return { value: new " + *s->name + "(), size: 0 }");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("this.model.get(value)");
    WriteLineIndent("return { value: value, size: fbeFullSize }");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Move to the next struct value");
    WriteLineIndent(" * @this {!" + *s->name + "Model}");
    WriteLineIndent(" * @param {!number} prev Previous " + *s->name + " model size");
    WriteLineIndent(" */");
    WriteLineIndent("next (prev) {");
    Indent(1);
    WriteLineIndent("this.model.fbeShift(prev)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model exports
    WriteLine();
    WriteLineIndent("exports." + *s->name + "Model = " + *s->name + "Model");
}

void GeneratorJavaScript::GenerateStructFinalModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent(" */");
    WriteLineIndent("class FinalModel" + *s->name + " extends fbe.FinalModel {");
    Indent(1);

    // Generate struct final model constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("/**");
        WriteLineIndent(" * Initialize final model with the given buffer and offset");
        WriteLineIndent(" * @param {!fbe.ReadBuffer|!fbe.WriteBuffer} buffer Buffer");
        WriteLineIndent(" * @param {!number} offset Offset");
        WriteLineIndent(" * @constructor");
        WriteLineIndent(" */");
        WriteLineIndent("constructor (buffer, offset) {");
        Indent(1);
        WriteLineIndent("super(buffer, offset)");
        if (s->base && !s->base->empty())
            WriteLineIndent("this._parent = new " + ConvertTypeFieldName(*s->base, true) + "(buffer, 0)");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this._" + *field->name + " = new " + ConvertTypeFieldInitialization(*field, "0", true));
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("/**");
        WriteLineIndent(" * Get the " + *s->base + " final model");
        WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
        WriteLineIndent(" * @returns {!" + ConvertTypeFieldName(*s->base, true) + "} " + *s->base + " field model");
        WriteLineIndent(" */");
        WriteLineIndent("get parent () {");
        Indent(1);
        WriteLineIndent("return this._parent");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("/**");
            WriteLineIndent(" * Get the " + *field->name + " final model");
            WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
            WriteLineIndent(" * @returns {!" + ConvertTypeFieldDeclaration(*field, true) + "} " + *field->name + " final model");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *field->name + " () {");
            Indent(1);
            WriteLineIndent("return this._" + *field->name);
            Indent(-1);
            WriteLineIndent("}");
            WriteLine();
        }
    }

    // Generate struct final model FBE properties
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the allocation size");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" * @returns {!number} Allocation size");
    WriteLineIndent(" */");
    WriteLineIndent("fbeAllocationSize (fbeValue) {");
    Indent(1);
    WriteIndent("return 0");
    if (s->base && !s->base->empty())
        Write(" + this.parent.fbeAllocationSize(fbeValue)");
    if (s->body)
        for (const auto& field : s->body->fields)
            Write(" + this." + *field->name + ".fbeAllocationSize(fbeValue." + *field->name + ")");
    WriteLine();
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the final type");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Final type");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeType () {");
    Indent(1);
    WriteLineIndent("return FinalModel" + *s->name + ".fbeType");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the final type (static)");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Final type");
    WriteLineIndent(" */");
    WriteLineIndent("static get fbeType () {");
    Indent(1);
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("return " + ConvertTypeFieldName(*s->base, true) + ".fbeType");
    else
        WriteLineIndent("return " + std::to_string(s->type));
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model verify() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct value is valid");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error");
    WriteLineIndent(" */");
    WriteLineIndent("verify () {");
    Indent(1);
    WriteLineIndent("this._buffer.shift(this.fbeOffset)");
    WriteLineIndent("let fbeResult = this.verifyFields()");
    WriteLineIndent("this._buffer.unshift(this.fbeOffset)");
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model verifyFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct fields are valid");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @returns {!number} Final model size or Number.MAX_SAFE_INTEGER in case of any error");
    WriteLineIndent(" */");
    WriteLineIndent("verifyFields () {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("let fbeCurrentOffset = 0");
        WriteLineIndent("let fbeFieldSize");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("this.parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeFieldSize = this.parent.verifyFields()");
            WriteLineIndent("if (fbeFieldSize === Number.MAX_SAFE_INTEGER) {");
            Indent(1);
            WriteLineIndent("return Number.MAX_SAFE_INTEGER");
            Indent(-1);
            WriteLineIndent("}");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("this." + *field->name + ".fbeOffset = fbeCurrentOffset");
                WriteLineIndent("fbeFieldSize = this." + *field->name + ".verify()");
                WriteLineIndent("if (fbeFieldSize === Number.MAX_SAFE_INTEGER) {");
                Indent(1);
                WriteLineIndent("return Number.MAX_SAFE_INTEGER");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentOffset");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model get() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct value");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value, defaults is new " + *s->name + "()");
    WriteLineIndent(" * @returns {!object} Result struct value and its size");
    WriteLineIndent(" */");
    WriteLineIndent("get (fbeValue = new " + *s->name + "()) {");
    Indent(1);
    WriteLineIndent("this._buffer.shift(this.fbeOffset)");
    WriteLineIndent("let fbeSize = this.getFields(fbeValue)");
    WriteLineIndent("this._buffer.unshift(this.fbeOffset)");
    WriteLineIndent("return { value: fbeValue, size: fbeSize }");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model getFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the struct fields values");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" * @returns {!number} Struct size");
    WriteLineIndent(" */");
    WriteLineIndent("getFields (fbeValue) {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("let fbeCurrentOffset = 0");
        WriteLineIndent("let fbeCurrentSize = 0");
        WriteLineIndent("let fbeResult");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("this.parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeResult = this.parent.getFields(fbeValue)");
            WriteLineIndent("// noinspection JSUnusedAssignment");
            WriteLineIndent("fbeCurrentOffset += fbeResult");
            WriteLineIndent("fbeCurrentSize += fbeResult");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("this." + *field->name + ".fbeOffset = fbeCurrentOffset");
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeResult = this." + *field->name + ".get(fbeValue." + *field->name + ")");
                else
                {
                    WriteLineIndent("fbeResult = this." + *field->name + ".get()");
                    WriteLineIndent("fbeValue." + *field->name + " = fbeResult.value");
                }
                WriteLineIndent("// noinspection JSUnusedAssignment");
                WriteLineIndent("fbeCurrentOffset += fbeResult.size");
                WriteLineIndent("fbeCurrentSize += fbeResult.size");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model set() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct value");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @param {!" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" * @returns {!number} Final model size");
    WriteLineIndent(" */");
    WriteLineIndent("set (fbeValue) {");
    Indent(1);
    WriteLineIndent("this._buffer.shift(this.fbeOffset)");
    WriteLineIndent("let fbeSize = this.setFields(fbeValue)");
    WriteLineIndent("this._buffer.unshift(this.fbeOffset)");
    WriteLineIndent("return fbeSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model setFields() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Set the struct fields values");
    WriteLineIndent(" * @this {!FinalModel" + *s->name + "}");
    WriteLineIndent(" * @param {" + *s->name + "} fbeValue " + *s->name + " value");
    WriteLineIndent(" * @returns {!number} Final model size");
    WriteLineIndent(" */");
    WriteLineIndent("setFields (fbeValue) {");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("let fbeCurrentOffset = 0");
        WriteLineIndent("let fbeCurrentSize = 0");
        WriteLineIndent("let fbeFieldSize");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("this.parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeFieldSize = this.parent.setFields(fbeValue)");
            WriteLineIndent("// noinspection JSUnusedAssignment");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("this." + *field->name + ".fbeOffset = fbeCurrentOffset");
                WriteLineIndent("fbeFieldSize = this." + *field->name + ".set(fbeValue." + *field->name + ")");
                WriteLineIndent("// noinspection JSUnusedAssignment");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model exports
    WriteLine();
    WriteLineIndent("exports.FinalModel" + *s->name + " = FinalModel" + *s->name);
}

void GeneratorJavaScript::GenerateStructModelFinal(const std::shared_ptr<StructType>& s)
{
    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent(" */");
    WriteLineIndent("class " + *s->name + "FinalModel extends fbe.Model {");
    Indent(1);

    // Generate struct model final constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize final model with the given buffer");
    WriteLineIndent(" * @param {!fbe.ReadBuffer|!fbe.WriteBuffer} buffer Read/Write buffer, defaults is new fbe.WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (buffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(buffer)");
    WriteLineIndent("this._model = new FinalModel" + *s->name + "(this.buffer, 8)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the model type");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @returns {!number} Model type");
    WriteLineIndent(" */");
    WriteLineIndent("get fbeType () {");
    Indent(1);
    WriteLineIndent("return " + *s->name + "FinalModel.fbeType");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Get the model type (static)");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @returns {!number} Model type");
    WriteLineIndent(" */");
    WriteLineIndent("static get fbeType () {");
    Indent(1);
    WriteLineIndent("return FinalModel" + *s->name + ".fbeType");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Check if the struct value is valid");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @returns {!boolean} Model valid state");
    WriteLineIndent(" */");
    WriteLineIndent("verify () {");
    Indent(1);
    WriteLineIndent("if ((this.buffer.offset + this._model.fbeOffset) > this.buffer.size) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.readUInt32(this._model.fbeOffset - 8)");
    WriteLineIndent("let fbeStructType = this.readUInt32(this._model.fbeOffset - 4)");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType !== this.fbeType)) {");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("return ((8 + this._model.verify()) === fbeStructSize)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final serialize() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Serialize the struct value");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value");
    WriteLineIndent(" * @return {!number} Struct size");
    WriteLineIndent(" */");
    WriteLineIndent("serialize (value) {");
    Indent(1);
    WriteLineIndent("let fbeInitialSize = this.buffer.size");
    WriteLine();
    WriteLineIndent("let fbeStructType = this.fbeType");
    WriteLineIndent("let fbeStructSize = 8 + this._model.fbeAllocationSize(value)");
    WriteLineIndent("let fbeStructOffset = this.buffer.allocate(fbeStructSize) - this.buffer.offset");
    WriteLineIndent("console.assert(((this.buffer.offset + fbeStructOffset + fbeStructSize) <= this.buffer.size), 'Model is broken!')");
    WriteLineIndent("if ((this.buffer.offset + fbeStructOffset + fbeStructSize) > this.buffer.size) {");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("fbeStructSize = 8 + this._model.set(value)");
    WriteLineIndent("this.buffer.resize(fbeInitialSize + fbeStructSize)");
    WriteLine();
    WriteLineIndent("this.writeUInt32(this._model.fbeOffset - 8, fbeStructSize)");
    WriteLineIndent("this.writeUInt32(this._model.fbeOffset - 4, fbeStructType)");
    WriteLine();
    WriteLineIndent("return fbeStructSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final deserialize() methods
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Deserialize the struct value");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value, defaults is new " + *s->name + "()");
    WriteLineIndent(" * @return {!object} Deserialized " + *s->name + " value and its size");
    WriteLineIndent(" */");
    WriteLineIndent("deserialize (value = new " + *s->name + "()) {");
    Indent(1);
    WriteLineIndent("console.assert(((this.buffer.offset + this._model.fbeOffset) <= this.buffer.size), 'Model is broken!')");
    WriteLineIndent("if ((this.buffer.offset + this._model.fbeOffset) > this.buffer.size) {");
    Indent(1);
    WriteLineIndent("return { value: new " + *s->name + "(), size: 0 }");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeStructSize = this.readUInt32(this._model.fbeOffset - 8)");
    WriteLineIndent("let fbeStructType = this.readUInt32(this._model.fbeOffset - 4)");
    WriteLineIndent("console.assert(((fbeStructSize > 0) && (fbeStructType === this.fbeType)), 'Model is broken!')");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType !== this.fbeType)) {");
    Indent(1);
    WriteLineIndent("return { value: new " + *s->name + "(), size: 8 }");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("let fbeResult = this._model.get(value)");
    WriteLineIndent("return { value: fbeResult.value, size: (8 + fbeResult.size) }");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Move to the next struct value");
    WriteLineIndent(" * @this {!" + *s->name + "FinalModel}");
    WriteLineIndent(" * @param {!number} prev Previous " + *s->name + " model size");
    WriteLineIndent(" */");
    WriteLineIndent("next (prev) {");
    Indent(1);
    WriteLineIndent("this._model.fbeShift(prev)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final exports
    WriteLine();
    WriteLineIndent("exports." + *s->name + "FinalModel = " + *s->name + "FinalModel");
}

void GeneratorJavaScript::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    // Generate protocol version constants
    WriteLine();
    WriteLineIndent("// Protocol major version");
    WriteLineIndent("const ProtocolVersionMajor = " + std::to_string(p->version->major));
    WriteLineIndent("// Protocol minor version");
    WriteLineIndent("const ProtocolVersionMinor = " + std::to_string(p->version->minor));

    // Generate sender exports
    WriteLine();
    WriteLineIndent("exports.ProtocolVersionMajor = ProtocolVersionMajor");
    WriteLineIndent("exports.ProtocolVersionMinor = ProtocolVersionMinor");
}

void GeneratorJavaScript::GenerateSender(const std::shared_ptr<Package>& p, bool final)
{
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate sender begin
    WriteLine();
    WriteLineIndent("/**");
    if (final)
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " final sender");
    else
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " sender");
    WriteLineIndent(" */");
    WriteLineIndent("class " + sender + " extends fbe.Sender {");
    Indent(1);

    // Generate sender constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize " + *p->name + " sender with the given buffer");
    WriteLineIndent(" * @param {!fbe.WriteBuffer} buffer Write buffer, defaults is new fbe.WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (buffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Sender = new " + *import + "." + sender + "(this.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("this._" + *s->name + "Model = new " + *s->name + "" + model + "(this.buffer)");
    }
    WriteLineIndent("this.onSendHandler = this.onSend");
    WriteLineIndent("this.onSendLogHandler = this.onSendLog");
    Indent(-1);
    WriteLineIndent("}");

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported senders");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Get imported " + *import + " sender");
            WriteLineIndent(" * @this {!" + sender + "}");
            WriteLineIndent(" * @returns {!" + *import + "." + sender + "} " + *import + " sender");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *import + "Sender () {");
            Indent(1);
            WriteLineIndent("return this._" + *import + "Sender");
            Indent(-1);
            WriteLineIndent("}");
        }
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * Get " + *s->name + " model");
                WriteLineIndent(" * @this {!" + sender + "}");
                WriteLineIndent(" * @returns {!" + *s->name + "Model} " + *s->name + " model");
                WriteLineIndent(" */");
                WriteLineIndent("get " + *s->name + "Model () {");
                Indent(1);
                WriteLineIndent("return this._" + *s->name + "Model");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender methods
    WriteLine();
    WriteLineIndent("// Send methods");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Send value");
    WriteLineIndent(" * @this {!" + sender + "}");
    WriteLineIndent(" * @param {!object} value Value to send");
    WriteLineIndent(" * @returns {!number} Sent bytes");
    WriteLineIndent(" */");
    WriteLineIndent("send (value) {");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("if ((value instanceof " + *s->name + ") && (value.fbeType === this." + *s->name + "Model.fbeType)) {");
                Indent(1);
                WriteLineIndent("return this.send_" + *s->name + "(value)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }
    if (p->import)
    {
        WriteLineIndent("let result = 0");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = this._" + *import + "Sender.send(value)");
            WriteLineIndent("if (result > 0) {");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * Send " + *s->name + " value");
                WriteLineIndent(" * @this {!" + sender + "}");
                WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value to send");
                WriteLineIndent(" * @returns {!number} Sent bytes");
                WriteLineIndent(" */");
                WriteLineIndent("send_" + *s->name + " (value) { // eslint-disable-line");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("let serialized = this." + *s->name + "Model.serialize(value)");
                WriteLineIndent("console.assert((serialized > 0), '" + *p->name + "." + *s->name + " serialization failed!')");
                WriteLineIndent("console.assert(this." + *s->name + "Model.verify(), '" + *p->name + "." + *s->name + " validation failed!')");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this.logging) {");
                Indent(1);
                WriteLineIndent("this.onSendLog(value.toString())");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return this.sendSerialized(serialized)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Send message handler");
    WriteLineIndent(" * @this {!" + sender + "}");
    WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
    WriteLineIndent(" * @param {!number} offset Buffer offset");
    WriteLineIndent(" * @param {!number} size Buffer size");
    WriteLineIndent(" */");
    WriteLineIndent("onSend (buffer, offset, size) {");
    Indent(1);
    WriteLineIndent("console.assert(true, '" + *p->name + ".Sender.onSend() not implemented!')");
    WriteLineIndent("debugger // eslint-disable-line");
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup send message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup send message handler");
    WriteLineIndent(" * @this {!" + sender + "}");
    WriteLineIndent(" * @param {!function} handler Send message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onSendHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onSend = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Sender.onSendHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup send log message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup send log message handler");
    WriteLineIndent(" * @this {!" + sender + "}");
    WriteLineIndent(" * @param {!function} handler Send log message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onSendLogHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onSendLog = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Sender.onSendLogHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate sender end
    Indent(-1);
    WriteLineIndent("}");

    // Generate sender exports
    WriteLine();
    WriteLineIndent("exports." + sender + " = " + sender);
}

void GeneratorJavaScript::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
{
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate receiver begin
    WriteLine();
    WriteLineIndent("/**");
    if (final)
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " final receiver");
    else
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " receiver");
    WriteLineIndent(" */");
    WriteLineIndent("class " + receiver + " extends fbe.Receiver {");
    Indent(1);

    // Generate receiver constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize " + *p->name + " receiver with the given buffer");
    WriteLineIndent(" * @param {!fbe.WriteBuffer} buffer Write buffer, defaults is new WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (buffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Receiver = new " + *import + "." + receiver + "(this.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("this._" + *s->name + "Value = new " + *s->name + "()");
                WriteLineIndent("this._" + *s->name + "Model = new " + *s->name + model + "()");
            }
        }
    }
    WriteLineIndent("this.onReceiveLogHandler = this.onReceiveLog");
    Indent(-1);
    WriteLineIndent("}");

    // Generate imported receiver accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported receivers");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Get imported " + *import + " receiver");
            WriteLineIndent(" * @this {!" + receiver + "}");
            WriteLineIndent(" * @returns {" + receiver + "} " + *import + " receiver");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *import + "Receiver () {");
            Indent(1);
            WriteLineIndent("return this._" + *import + "Receiver");
            Indent(-1);
            WriteLineIndent("}");
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Set imported " + *import + " receiver");
            WriteLineIndent(" * @this {!" + receiver + "}");
            WriteLineIndent(" * @param {" + receiver + "} receiver " + *import + " receiver");
            WriteLineIndent(" */");
            WriteLineIndent("set " + *import + "Receiver (receiver) {");
            Indent(1);
            WriteLineIndent("this._" + *import + "Receiver = receiver");
            Indent(-1);
            WriteLineIndent("}");
        }
    }

    // Generate receiver handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("// Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * " + *s->name + " receive handler");
                WriteLineIndent(" * @this {!" + receiver + "}");
                WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " received value");
                WriteLineIndent(" */");
                WriteLineIndent("onReceive_" + *s->name + " (value) {}  // eslint-disable-line");
            }
        }
        WriteLine();
    }

    // Generate receiver message handler
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *p->name + " receive message handler");
    WriteLineIndent(" * @this {!" + receiver + "}");
    WriteLineIndent(" * @param {!number} type Message type");
    WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
    WriteLineIndent(" * @param {!number} offset Buffer offset");
    WriteLineIndent(" * @param {!number} size Buffer size");
    WriteLineIndent(" * @returns {!boolean} Success flag");
    WriteLineIndent(" */");
    WriteLineIndent("onReceive (type, buffer, offset, size) {");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type) {");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".fbeType: {");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent("this._" + *s->name + "Model.attachBuffer(buffer, offset)");
                WriteLineIndent("console.assert(this._" + *s->name + "Model.verify(), '" + *p->name + "." + *s->name + " validation failed!')");
                WriteLineIndent("let deserialized = this._" + *s->name + "Model.deserialize(this._" + *s->name + "Value)");
                WriteLineIndent("console.assert((deserialized.size > 0), '" + *p->name + "." + *s->name + " deserialization failed!')");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this.logging) {");
                Indent(1);
                WriteLineIndent("this.onReceiveLog(this._" + *s->name + "Value.toString())");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("this.onReceive_" + *s->name + "(this._" + *s->name + "Value)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("// noinspection RedundantIfStatementJS");
            WriteLineIndent("if ((this." + *import + "Receiver != null) && this." + *import + "Receiver.onReceive(type, buffer, offset, size)) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLine();
    }
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup receive log message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup receive log message handler");
    WriteLineIndent(" * @this {!" + receiver + "}");
    WriteLineIndent(" * @param {!function} handler Receive log message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onReceiveLogHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onReceiveLog = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Receiver.onReceiveLogHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver exports
    WriteLine();
    WriteLineIndent("exports." + receiver + " = " + receiver + "");
}

void GeneratorJavaScript::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
{
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate proxy begin
    WriteLine();
    WriteLineIndent("/**");
    if (final)
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " final proxy");
    else
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " proxy");
    WriteLineIndent(" */");
    WriteLineIndent("class " + proxy + " extends fbe.Receiver {");
    Indent(1);

    // Generate proxy constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize " + *p->name + " proxy with the given buffer");
    WriteLineIndent(" * @param {!fbe.WriteBuffer} buffer Write buffer, defaults is new WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (buffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Proxy = new " + *import + "." + proxy + "(this.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("this._" + *s->name + "Model = new " + *s->name + model + "()");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported proxy");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Get imported " + *import + " proxy");
            WriteLineIndent(" * @this {!" + proxy + "}");
            WriteLineIndent(" * @returns {" + proxy + "} " + *import + " proxy");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *import + "Proxy () {");
            Indent(1);
            WriteLineIndent("return this._" + *import + "Proxy");
            Indent(-1);
            WriteLineIndent("}");
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Set imported " + *import + " proxy");
            WriteLineIndent(" * @this {!" + proxy + "}");
            WriteLineIndent(" * @param {" + proxy + "} proxy " + *import + " proxy");
            WriteLineIndent(" */");
            WriteLineIndent("set " + *import + "Proxy (proxy) {");
            Indent(1);
            WriteLineIndent("this._" + *import + "Proxy = proxy");
            Indent(-1);
            WriteLineIndent("}");
        }
    }

    // Generate proxy handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("// Proxy handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * " + *s->name + " proxy handler");
                WriteLineIndent(" * @this {!" + proxy + "}");
                WriteLineIndent(" * @param {!" + *s->name + "} model " + *s->name + " model");
                WriteLineIndent(" * @param {!number} type Message type");
                WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
                WriteLineIndent(" * @param {!number} offset Buffer offset");
                WriteLineIndent(" * @param {!number} size Buffer size");
                WriteLineIndent(" */");
                WriteLineIndent("onProxy_" + *s->name + " (model, type, buffer, offset, size) {}  // eslint-disable-line");
            }
        }
        WriteLine();
    }

    // Generate proxy message handler
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *p->name + " receive message handler");
    WriteLineIndent(" * @this {!" + proxy + "}");
    WriteLineIndent(" * @param {!number} type Message type");
    WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
    WriteLineIndent(" * @param {!number} offset Buffer offset");
    WriteLineIndent(" * @param {!number} size Buffer size");
    WriteLineIndent(" * @returns {!boolean} Success flag");
    WriteLineIndent(" */");
    WriteLineIndent("onReceive (type, buffer, offset, size) {");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type) {");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".fbeType: {");
                Indent(1);
                WriteLineIndent("// Attach the FBE stream to the proxy model");
                WriteLineIndent("this._" + *s->name + "Model.attachBuffer(buffer, offset)");
                WriteLineIndent("console.assert(this._" + *s->name + "Model.verify(), '" + *p->name + "." + *s->name + " validation failed!')");
                WriteLine();
                WriteLineIndent("let fbeBegin = this._" + *s->name + "Model.model.getBegin()");
                WriteLineIndent("if (fbeBegin === 0) {");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("// Call proxy handler");
                WriteLineIndent("this.onProxy_" + *s->name + "(this._" + *s->name + "Model, type, buffer, offset, size)");
                WriteLineIndent("this._" + *s->name + "Model.model.getEnd(fbeBegin)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("// noinspection RedundantIfStatementJS");
            WriteLineIndent("if ((this." + *import + "Proxy != null) && this." + *import + "Proxy.onReceive(type, buffer, offset, size)) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLine();
    }
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy exports
    WriteLine();
    WriteLineIndent("exports." + proxy + " = " + proxy + "");
}

void GeneratorJavaScript::GenerateClient(const std::shared_ptr<Package>& p, bool final)
{
    std::string client = (final ? "FinalClient" : "Client");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate client begin
    WriteLine();
    WriteLineIndent("/**");
    if (final)
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " final client");
    else
        WriteLineIndent(" * Fast Binary Encoding " + *p->name + " client");
    WriteLineIndent(" */");
    WriteLineIndent("class " + client + " extends fbe.Client {");
    Indent(1);

    // Generate sender constructor
    WriteLineIndent("/**");
    WriteLineIndent(" * Initialize " + *p->name + " client with the given buffers");
    WriteLineIndent(" * @param {!fbe.WriteBuffer} sendBuffer Send buffer, defaults is new fbe.WriteBuffer()");
    WriteLineIndent(" * @param {!fbe.WriteBuffer} receiveBuffer Receive buffer, defaults is new fbe.WriteBuffer()");
    WriteLineIndent(" * @constructor");
    WriteLineIndent(" */");
    WriteLineIndent("constructor (sendBuffer = new fbe.WriteBuffer(), receiveBuffer = new fbe.WriteBuffer()) {");
    Indent(1);
    WriteLineIndent("super(sendBuffer, receiveBuffer, " + std::string(final ? "true" : "false") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client = new " + *import + "." + client + "(this.sendBuffer, this.receiveBuffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("this._" + *s->name + "SenderModel = new " + *s->name + "" + model + "(this.sendBuffer)");
                WriteLineIndent("this._" + *s->name + "ReceiverValue = new " + *s->name + "()");
                WriteLineIndent("this._" + *s->name + "ReceiverModel = new " + *s->name + model + "()");
            }
        }
    }
    WriteLineIndent("this.onSendHandler = this.onSend");
    WriteLineIndent("this.onSendLogHandler = this.onSendLog");
    WriteLineIndent("this.onReceiveLogHandler = this.onReceiveLog");
    WriteLineIndent("this._timestamp = 0");
    WriteLineIndent("this._requests_by_id = new Map()");
    WriteLineIndent("this._requests_by_timestamp = new Map()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate imported clients accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported clients");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("/**");
            WriteLineIndent(" * Get imported " + *import + " client");
            WriteLineIndent(" * @this {!" + client + "}");
            WriteLineIndent(" * @returns {!" + *import + "." + client + "} " + *import + " client");
            WriteLineIndent(" */");
            WriteLineIndent("get " + *import + "Client () {");
            Indent(1);
            WriteLineIndent("return this._" + *import + "Client");
            Indent(-1);
            WriteLineIndent("}");
        }
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * Get " + *s->name + " model");
                WriteLineIndent(" * @this {!" + client + "}");
                WriteLineIndent(" * @returns {!" + *s->name + "Model} " + *s->name + " sender model");
                WriteLineIndent(" */");
                WriteLineIndent("get " + *s->name + "SenderModel () {");
                Indent(1);
                WriteLineIndent("return this._" + *s->name + "SenderModel");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate reset and watchdog methods
    WriteLine();
    WriteLineIndent("// Reset and watchdog methods");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Reset the client");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" */");
    WriteLineIndent("reset () {");
    Indent(1);
    WriteLineIndent("super.reset()");
    WriteLineIndent("this.resetRequests()");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Watchdog for timeouts");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!number} utc UTC timestamp");
    WriteLineIndent(" */");
    WriteLineIndent("watchdog (utc) {");
    Indent(1);
    WriteLineIndent("this.watchdogRequests(utc)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate sender methods
    WriteLine();
    WriteLineIndent("// Send methods");
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Send value");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!object} value Value to send");
    WriteLineIndent(" * @returns {!number} Sent bytes");
    WriteLineIndent(" */");
    WriteLineIndent("send (value) {");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("if ((value instanceof " + *s->name + ") && (value.fbeType === this." + *s->name + "SenderModel.fbeType)) {");
                Indent(1);
                WriteLineIndent("return this.send_" + *s->name + "(value)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }
    if (p->import)
    {
        WriteLineIndent("let result = 0");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = this._" + *import + "Client.send(value)");
            WriteLineIndent("if (result > 0) {");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * Send " + *s->name + " value");
                WriteLineIndent(" * @this {!" + client + "}");
                WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " value to send");
                WriteLineIndent(" * @returns {!number} Sent bytes");
                WriteLineIndent(" */");
                WriteLineIndent("send_" + *s->name + " (value) { // eslint-disable-line");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("let serialized = this." + *s->name + "SenderModel.serialize(value)");
                WriteLineIndent("console.assert((serialized > 0), '" + *p->name + "." + *s->name + " serialization failed!')");
                WriteLineIndent("console.assert(this." + *s->name + "SenderModel.verify(), '" + *p->name + "." + *s->name + " validation failed!')");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this.logging) {");
                Indent(1);
                WriteLineIndent("this.onSendLog(value.toString())");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return this.sendSerialized(serialized)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Send message handler");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
    WriteLineIndent(" * @param {!number} offset Buffer offset");
    WriteLineIndent(" * @param {!number} size Buffer size");
    WriteLineIndent(" */");
    WriteLineIndent("onSend (buffer, offset, size) {");
    Indent(1);
    WriteLineIndent("console.assert(true, '" + *p->name + ".Client.onSend() not implemented!')");
    WriteLineIndent("debugger // eslint-disable-line");
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup send message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup send message handler");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!function} handler Send message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onSendHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onSend = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client.onSendHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup send log message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup send log message handler");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!function} handler Send log message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onSendLogHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onSendLog = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client.onSendLogHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("// Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("/**");
                WriteLineIndent(" * " + *s->name + " receive handler");
                WriteLineIndent(" * @this {!" + client + "}");
                WriteLineIndent(" * @param {!" + *s->name + "} value " + *s->name + " received value");
                WriteLineIndent(" */");
                WriteLineIndent("onReceive_" + *s->name + " (value) {}  // eslint-disable-line");
            }
        }
        WriteLine();
    }

    // Generate receiver message handler
    WriteLineIndent("/**");
    WriteLineIndent(" * " + *p->name + " receive message handler");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!number} type Message type");
    WriteLineIndent(" * @param {!Uint8Array} buffer Buffer to send");
    WriteLineIndent(" * @param {!number} offset Buffer offset");
    WriteLineIndent(" * @param {!number} size Buffer size");
    WriteLineIndent(" * @returns {!boolean} Success flag");
    WriteLineIndent(" */");
    WriteLineIndent("onReceive (type, buffer, offset, size) {");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type) {");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case " + *s->name + model + ".fbeType: {");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent("this._" + *s->name + "ReceiverModel.attachBuffer(buffer, offset)");
                WriteLineIndent("console.assert(this._" + *s->name + "ReceiverModel.verify(), '" + *p->name + "." + *s->name + " validation failed!')");
                WriteLineIndent("let deserialized = this._" + *s->name + "ReceiverModel.deserialize(this._" + *s->name + "ReceiverValue)");
                WriteLineIndent("console.assert((deserialized.size > 0), '" + *p->name + "." + *s->name + " deserialization failed!')");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this.logging) {");
                Indent(1);
                WriteLineIndent("this.onReceiveLog(this._" + *s->name + "ReceiverValue.toString())");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("this.onReceive_" + *s->name + "(this._" + *s->name + "ReceiverValue)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("// noinspection RedundantIfStatementJS");
            WriteLineIndent("if ((this." + *import + "Client != null) && this." + *import + "Client.onReceive(type, buffer, offset, size)) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLine();
    }
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate setup receive log message handler
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Setup receive log message handler");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!function} handler Receive log message handler");
    WriteLineIndent(" */");
    WriteLineIndent("set onReceiveLogHandler (handler) { // eslint-disable-line");
    Indent(1);
    WriteLineIndent("this.onReceiveLog = handler");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client.onReceiveLogHandler = handler");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate request methods
    WriteLine();
    WriteLineIndent("// Request methods");

    // Generate request value method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Request value");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!object} value Value to request");
    WriteLineIndent(" * @param {!number} timeout Timeout in milliseconds (default is 0)");
    WriteLineIndent(" * @returns {Promise} Response promise");
    WriteLineIndent(" */");
    WriteLineIndent("request (value, timeout = 0) {");
    Indent(1);
    WriteLineIndent("let promise = new fbe.DeferredPromise()");
    WriteLineIndent("let current = Date.now()");
    WriteLine();
    WriteLineIndent("// Send the request message");
    WriteLineIndent("let serialized = this.send(value)");
    WriteLineIndent("if (serialized > 0) {");
    Indent(1);
    WriteLineIndent("// Calculate the unique timestamp");
    WriteLineIndent("this._timestamp = (current <= this._timestamp) ? this._timestamp + 1 : current");
    WriteLine();
    WriteLineIndent("// Register the request");
    WriteLineIndent("this._requests_by_id.set(value.id, [this._timestamp, timeout, promise])");
    WriteLineIndent("if (timeout > 0) {");
    Indent(1);
    WriteLineIndent("this._requests_by_timestamp.set(this._timestamp, value.id)");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("} else {");
    Indent(1);
    WriteLineIndent("promise.reject(new Error('Send request failed!'))");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("return promise");
    Indent(-1);
    WriteLineIndent("}");

    // Generate response value method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Response value");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!object} value Value to response");
    WriteLineIndent(" * @returns {!boolean} Response handle flag");
    WriteLineIndent(" */");
    WriteLineIndent("response (value) {");
    Indent(1);
    WriteLineIndent("let item = this._requests_by_id.get(value.id)");
    WriteLineIndent("if (item != null) {");
    Indent(1);
    WriteLineIndent("let timestamp = item[0]");
    WriteLineIndent("let promise = item[2]");
    WriteLineIndent("promise.resolve(value)");
    WriteLineIndent("this._requests_by_id.delete(value.id)");
    WriteLineIndent("this._requests_by_timestamp.delete(timestamp)");
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("// noinspection RedundantIfStatementJS");
            WriteLineIndent("if (this._" + *import + "Client.response(value)) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLine();
    }
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate reject value method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Reject value");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!object} value Value to reject");
    WriteLineIndent(" * @returns {!boolean} Reject handle flag");
    WriteLineIndent(" */");
    WriteLineIndent("reject (value) {");
    Indent(1);
    WriteLineIndent("let item = this._requests_by_id.get(value.id)");
    WriteLineIndent("if (item != null) {");
    Indent(1);
    WriteLineIndent("let timestamp = item[0]");
    WriteLineIndent("let promise = item[2]");
    WriteLineIndent("promise.reject(value)");
    WriteLineIndent("this._requests_by_id.delete(value.id)");
    WriteLineIndent("this._requests_by_timestamp.delete(timestamp)");
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("// noinspection RedundantIfStatementJS");
            WriteLineIndent("if (this._" + *import + "Client.reject(value)) {");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("}");
        }
        WriteLine();
    }
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate reset client requests method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Reset client requests");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" */");
    WriteLineIndent("resetRequests () {");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client.resetRequests()");
        WriteLine();
    }
    WriteLineIndent("for (let [, value] of this._requests_by_id) {");
    Indent(1);
    WriteLineIndent("value[2].reject(new Error('Reset client!'))");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("this._requests_by_id.clear()");
    WriteLineIndent("this._requests_by_timestamp.clear()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate watchdog client requests method
    WriteLine();
    WriteLineIndent("/**");
    WriteLineIndent(" * Watchdog client requests for timeouts");
    WriteLineIndent(" * @this {!" + client + "}");
    WriteLineIndent(" * @param {!number} utc UTC timestamp in milliseconds");
    WriteLineIndent(" */");
    WriteLineIndent("watchdogRequests (utc) {");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("this._" + *import + "Client.watchdogRequests(utc)");
        WriteLine();
    }
    WriteLineIndent("for (let [, value] of this._requests_by_timestamp) {");
    Indent(1);
    WriteLineIndent("let item = this._requests_by_id.get(value)");
    WriteLineIndent("let id = value");
    WriteLineIndent("let timestamp = item[0]");
    WriteLineIndent("let timespan = item[1]");
    WriteLineIndent("if ((timestamp + timespan) <= utc) {");
    Indent(1);
    WriteLineIndent("let promise = item[2]");
    WriteLineIndent("promise.reject(new Error('Timeout!'))");
    WriteLineIndent("this._requests_by_id.delete(id)");
    WriteLineIndent("this._requests_by_timestamp.delete(timestamp)");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("}");

    // Generate client end
    Indent(-1);
    WriteLineIndent("}");

    // Generate client exports
    WriteLine();
    WriteLineIndent("exports." + client + " = " + client);
}

bool GeneratorJavaScript::IsPrimitiveType(const std::string& type)
{
    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double"));
}

bool GeneratorJavaScript::IsJavaScriptType(const std::string& type)
{
    return IsPrimitiveType(type) || (type == "bytes") || (type == "string") || (type == "timestamp");
}

std::string GeneratorJavaScript::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJavaScript::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "UInt8";
    else if (type == "wchar")
        return "UInt32";
    else if (type == "int8")
        return "Int8";
    else if (type == "uint8")
        return "UInt8";
    else if (type == "int16")
        return "Int16";
    else if (type == "uint16")
        return "UInt16";
    else if (type == "int32")
        return "Int32";
    else if (type == "uint32")
        return "UInt32";
    else if (type == "int64")
        return "Int64";
    else if (type == "uint64")
        return "UInt64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJavaScript::ConvertEnumConstant(const std::string& name, const std::string& type, const std::string& value)
{
    if (((type == "char") || (type == "wchar")) && CppCommon::StringUtils::StartsWith(value, "'"))
        return value + ".charCodeAt(0)";
    else if (type == "int64")
        return ConvertConstantInt64(value);
    else if (type == "uint64")
        return ConvertConstantUInt64(value);
    else if (type.empty())
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (!flags.empty())
        {
            std::string result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                result += (first ? "" : " | ") + name + "." + CppCommon::StringUtils::ToTrim(it);
                first = false;
            }
            return result;
        }
    }

    return value;
}

std::string GeneratorJavaScript::ConvertTypeName(const std::string& type, bool optional)
{
    if (type == "bool")
        return "boolean";
    else if ((type == "byte") || (type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64") || (type == "float") || (type == "double"))
        return "number";
    else if (type == "decimal")
        return "Big";
    else if (type == "bytes")
        return "Uint8Array";
    else if (type == "timestamp")
        return "Date";
    else if ((type == "char") || (type == "wchar") || (type == "string"))
        return "string";
    else if (type == "uuid")
        return "UUID";

    return type;
}

std::string GeneratorJavaScript::ConvertTypeName(const StructField& field)
{
    if (field.array)
        return "Array";
    else if (field.vector)
        return "Array";
    else if (field.list)
        return "Array";
    else if (field.set)
        return "Set";
    else if (field.map)
        return "Map";
    else if (field.hash)
        return "Map";

    return ConvertTypeName(*field.type, field.optional);
}

std::string GeneratorJavaScript::ConvertTypeFieldName(const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (type == "bool")
        return "fbe." + modelType + "ModelBool";
    else if (type == "byte")
        return "fbe." + modelType + "ModelByte";
    else if (type == "char")
        return "fbe." + modelType + "ModelChar";
    else if (type == "wchar")
        return "fbe." + modelType + "ModelWChar";
    else if (type == "int8")
        return "fbe." + modelType + "ModelInt8";
    else if (type == "uint8")
        return "fbe." + modelType + "ModelUInt8";
    else if (type == "int16")
        return "fbe." + modelType + "ModelInt16";
    else if (type == "uint16")
        return "fbe." + modelType + "ModelUInt16";
    else if (type == "int32")
        return "fbe." + modelType + "ModelInt32";
    else if (type == "uint32")
        return "fbe." + modelType + "ModelUInt32";
    else if (type == "int64")
        return "fbe." + modelType + "ModelInt64";
    else if (type == "uint64")
        return "fbe." + modelType + "ModelUInt64";
    else if (type == "float")
        return "fbe." + modelType + "ModelFloat";
    else if (type == "double")
        return "fbe." + modelType + "ModelDouble";
    else if (type == "decimal")
        return "fbe." + modelType + "ModelDecimal";
    else if (type == "timestamp")
        return "fbe." + modelType + "ModelTimestamp";
    else if (type == "uuid")
        return "fbe." + modelType + "ModelUUID";
    else if (type == "bytes")
        return "fbe." + modelType + "ModelBytes";
    else if (type == "string")
        return "fbe." + modelType + "ModelString";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + modelType + "Model" + ConvertTypeName(t, false);
}

std::string GeneratorJavaScript::ConvertTypeFieldDeclaration(const StructField& field, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return "fbe." + modelType + "ModelArray";
    else if (field.vector || field.list)
        return "fbe." + modelType + "ModelVector";
    else if (field.set)
        return "fbe." + modelType + "ModelSet";
    else if (field.map || field.hash)
        return "fbe." + modelType + "ModelMap";
    else if (field.optional)
        return "fbe." + modelType + "ModelOptional";
    else
        return ConvertTypeFieldName(*field.type, final);
}

std::string GeneratorJavaScript::ConvertTypeFieldInitialization(const std::string& type, bool optional, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (optional)
        return "fbe." + modelType + "ModelOptional(new " + ConvertTypeFieldInitialization(type, false, offset, final)+ ", buffer, " + offset + ")";
    else
        return ConvertTypeFieldName(type, final) + "(buffer, " + offset + ")";
}

std::string GeneratorJavaScript::ConvertTypeFieldInitialization(const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return "fbe." + modelType + "ModelArray(new " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ", " + std::to_string(field.N) + ")";
    else if (field.vector || field.list)
        return "fbe." + modelType + "ModelVector(new " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else if (field.set)
        return "fbe." + modelType + "ModelSet(new " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else if (field.map || field.hash)
        return "fbe." + modelType + "ModelMap(new " + ConvertTypeFieldInitialization(*field.key, false, offset, final) + ", new " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else
        return ConvertTypeFieldInitialization(*field.type, field.optional, offset, final);
}

std::string GeneratorJavaScript::ConvertConstant(const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return "undefined";
    else if (value == "min")
    {
        if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32"))
            return "0";
        else if (type == "int8")
            return "-128";
        else if (type == "int16")
            return "-32768";
        else if (type == "int32")
            return "-2147483648";
        else if (type == "int64")
            return ConvertConstantInt64("-9223372036854775808");
        else if (type == "uint64")
            return ConvertConstantUInt64("0");

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return "255";
        else if (type == "int8")
            return "127";
        else if (type == "uint8")
            return "255";
        else if (type == "int16")
            return "32767";
        else if (type == "uint16")
            return "65535";
        else if (type == "int32")
            return "2147483647";
        else if (type == "uint32")
            return "4294967295";
        else if (type == "int64")
            return ConvertConstantInt64("9223372036854775807");
        else if (type == "uint64")
            return ConvertConstantUInt64("18446744073709551615");

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return "new Date(0)";
    else if (value == "uuid0")
        return "UUID.nil()";
    else if (value == "uuid1")
        return "UUID.sequential()";
    else if (value == "uuid4")
        return "UUID.random()";
    else if (value == "utc")
        return "new Date(Date.now())";

    if (((type == "char") || (type == "wchar")) && !CppCommon::StringUtils::StartsWith(value, "'"))
        return "String.fromCharCode(" + value + ")";
    else if (type == "int64")
        return ConvertConstantInt64(value);
    else if (type == "uint64")
        return ConvertConstantUInt64(value);
    else if (type == "decimal")
        return "new Big('" + value + "')";
    else if (type == "string")
        return ConvertString(value);
    else if (type == "uuid")
        return "new UUID(" + ConvertString(value) + ")";

    if (IsJavaScriptType(type))
        return value;
    else
        return "new " + ConvertTypeName(type, false) + "(" + value + ")";
}

std::string GeneratorJavaScript::ConvertConstantInt64(const std::string& value)
{
    int64_t full = std::strtoll(value.c_str(), nullptr, 0);
    int32_t high = (int32_t)(full >> 32);
    uint32_t low = (uint32_t)full;
    return "new Int64(" + std::to_string(low) + ", " + std::to_string(high) + ")";
}

std::string GeneratorJavaScript::ConvertConstantUInt64(const std::string& value)
{
    uint64_t full = std::strtoull(value.c_str(), nullptr, 0);
    uint32_t high = (uint32_t)(full >> 32);
    uint32_t low = (uint32_t)full;
    return "new UInt64(" + std::to_string(low) + ", " + std::to_string(high) + ")";
}

std::string GeneratorJavaScript::ConvertDefault(const std::string& type)
{
    if (type == "bool")
        return "false";
    else if (type == "byte")
        return "0";
    else if (type == "bytes")
        return "new Uint8Array(0)";
    else if ((type == "char") || (type == "wchar"))
        return "'\\0'";
    else if ((type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32"))
        return "0";
    else if (type == "int64")
        return ConvertConstantInt64("0");
    else if (type == "uint64")
        return ConvertConstantUInt64("0");
    else if ((type == "float") || (type == "double"))
        return "0.0";
    else if (type == "decimal")
        return "new Big(0)";
    else if (type == "string")
        return "''";
    else if (type == "timestamp")
        return "new Date(0)";
    else if (type == "uuid")
        return "new UUID()";

    return "new " + type + "()";
}

std::string GeneratorJavaScript::ConvertDefault(const StructField& field)
{
    if (field.value)
        return ConvertConstant(*field.type, *field.value, field.optional);

    if (field.array || field.vector || field.list)
        return "[]";
    else if (field.set)
        return "new Set()";
    else if (field.map || field.hash)
        return "new Map()";
    else if (field.optional)
        return "undefined";

    return ConvertDefault(*field.type);
}

std::string GeneratorJavaScript::ConvertString(const std::string& value)
{
    std::string result;

    if (CppCommon::StringUtils::StartsWith(value, "\"") && CppCommon::StringUtils::EndsWith(value, "\"") && (value.size() >= 2))
        result = value.substr(1, value.size() - 2);
    else
        result = value;

    return "'" + result + "'";
}

std::string GeneratorJavaScript::ConvertVariable(const std::string& type, const std::string& name, bool optional)
{
    if ((type == "char") || (type == "wchar"))
        return "((" + name + " != null) ? " + name + ".charCodeAt(0) : null)";
    else if ((type == "int64") || (type == "uint64"))
        return "((" + name + " != null) ? " + name + ".toNumber() : null)";
    else if (type == "bytes")
        return "((" + name + " != null) ? Buffer.from(" + name + ").toString('base64') : null)";
    else if (type == "decimal")
        return "((" + name + " != null) ? " + name + ".toFixed() : null)";
    else if (type == "timestamp")
        return "((" + name + " != null) ? (" + name + ".getTime() * 1000000) : null)";
    else if (type == "uuid")
        return "((" + name + " != null) ? " + name + ".toString() : null)";
    else
        return "((" + name + " != null) ? " + name + " : null)";
}

void GeneratorJavaScript::CopyValueToVariable(const std::string& type, const std::string& name, const std::string& variable, bool optional)
{
    if ((type == "char") || (type == "wchar"))
    {
        WriteLineIndent("if ((typeof " + name + " === 'string') || (" + name + " instanceof String)) {");
        Indent(1);
        WriteLineIndent(variable + " = " + name);
        Indent(-1);
        WriteLineIndent("} else {");
        Indent(1);
        WriteLineIndent(variable + " = String.fromCharCode(" + name + ")");
        Indent(-1);
        WriteLineIndent("}");
    }
    else if (type == "int64")
        WriteLineIndent(variable + " = Int64.fromNumber(" + name + ")");
    else if (type == "uint64")
        WriteLineIndent(variable + " = UInt64.fromNumber(" + name + ")");
    else if (type == "bytes")
    {
        WriteLineIndent("if (typeof " + name + " === 'string') {");
        Indent(1);
        WriteLineIndent("// noinspection JSUnresolvedFunction");
        WriteLineIndent(variable + " = Uint8Array.from(Buffer.from(" + name + ", 'base64'))");
        Indent(-1);
        WriteLineIndent("} else {");
        Indent(1);
        WriteLineIndent(variable + " = Uint8Array.from(" + name + ")");
        Indent(-1);
        WriteLineIndent("}");
    }
    else if (type == "decimal")
        WriteLineIndent(variable + " = new Big(" + name + ")");
    else if (type == "timestamp")
    {
        WriteLineIndent("if (" + name + " instanceof Date) {");
        Indent(1);
        WriteLineIndent(variable + " = new Date(" + name + ".getTime())");
        Indent(-1);
        WriteLineIndent("} else {");
        Indent(1);
        WriteLineIndent(variable + " = new Date(Math.round(" + name + " / 1000000))");
        Indent(-1);
        WriteLineIndent("}");
    }
    else if (type == "uuid")
        WriteLineIndent(variable + " = new UUID(" + name + ")");
    else if (IsJavaScriptType(type))
        WriteLineIndent(variable + " = " + name);
    else
        WriteLineIndent(variable + " = " + ConvertTypeName(type, false) + ".fromObject(" + name + ")");
}

void GeneratorJavaScript::WriteOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        WriteLineIndent("result += " + name + " ? 'true' : 'false'");
    else if (type == "bytes")
        WriteLineIndent("result += 'bytes[' + " + name + ".length + ']'");
    else if ((type == "char") || (type == "wchar"))
        WriteLineIndent("result += \"'\" + " + name + ".toString() + \"'\"");
    else if (type == "decimal")
        WriteLineIndent("result += " + name + ".toFixed()");
    else if ((type == "string") || (type == "uuid"))
        WriteLineIndent("result += '\"' + " + name + ".toString() + '\"'");
    else if (type == "timestamp")
        WriteLineIndent("result += " + name + ".getTime() * 1000000");
    else
        WriteLineIndent("result += " + name + ".toString()");
}

void GeneratorJavaScript::WriteOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    if (optional || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid"))
    {
        WriteLineIndent("if (" + name + " != null) {");
        Indent(1);
        if (separate)
            WriteLineIndent("result += first ? '' : ','");
        WriteOutputStreamType(type, name, true);
        Indent(-1);
        WriteLineIndent("} else {");
        Indent(1);
        if (separate)
            WriteLineIndent("result += first ? '' : ','");
        WriteLineIndent("result += 'null'");
        Indent(-1);
        WriteLineIndent("}");
    }
    else
    {
        if (separate)
            WriteLineIndent("result += first ? '' : ','");
        WriteOutputStreamType(type, name, false);
    }
}

} // namespace FBE
